<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="高并发,Mina," />










<meta name="description" content="Mina案例源码 简单的TCPServer第一步：编写IoService编写IoService，按照上面的执行流程，我们首先需要编写IoService，IoService 本身既是服务端，又是客户端，我们这里编写服务端，所以使用IoAcceptor 实现，由于IoAcceptor 是与协议无关的，因为我们要编写TCPServer，所以我们使用IoAcceptor 的实现NioSocketAccep">
<meta property="og:type" content="article">
<meta property="og:title" content="网络通信框架（2）---Mina">
<meta property="og:url" content="http://yoursite.com/p/999bcd34.html">
<meta property="og:site_name" content="Molzhao">
<meta property="og:description" content="Mina案例源码 简单的TCPServer第一步：编写IoService编写IoService，按照上面的执行流程，我们首先需要编写IoService，IoService 本身既是服务端，又是客户端，我们这里编写服务端，所以使用IoAcceptor 实现，由于IoAcceptor 是与协议无关的，因为我们要编写TCPServer，所以我们使用IoAcceptor 的实现NioSocketAccep">
<meta property="og:image" content="https://molzhao-pic.oss-cn-beijing.aliyuncs.com/2020-06-20/%E5%8D%8F%E8%AE%AE%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8.png">
<meta property="article:published_time" content="2020-06-19T17:57:00.000Z">
<meta property="article:modified_time" content="2020-07-10T17:13:29.709Z">
<meta property="article:author" content="MolZhao">
<meta property="article:tag" content="高并发">
<meta property="article:tag" content="Mina">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://molzhao-pic.oss-cn-beijing.aliyuncs.com/2020-06-20/%E5%8D%8F%E8%AE%AE%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"我们没有找到任何搜索结果: ${query}","hits_stats":"找到约${hits}条结果 (用时 ${time}ms)"}
    }
  };
</script>



  <link rel="canonical" href="http://yoursite.com/p/999bcd34.html"/>





  <title>网络通信框架（2）---Mina | Molzhao</title>
  








<meta name="generator" content="Hexo 4.2.1"></head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Molzhao</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/index.html" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/index.html" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/index.html" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-commonweal">
          <a href="/404/index.html" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-heartbeat"></i> <br />
            
            公益404
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>

                

 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://yoursite.com/p/999bcd34.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="MolZhao">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Molzhao">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">网络通信框架（2）---Mina</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2020-06-20T01:57:00+08:00">
                2020-06-20
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E4%BA%92%E8%81%94%E7%BD%91%E6%9E%B6%E6%9E%84/index.html" itemprop="url" rel="index">
                    <span itemprop="name">互联网架构</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/p/999bcd34.html#comments" itemprop="discussionUrl">
                  <span class="post-comments-count valine-comment-count" data-xid="/p/999bcd34.html" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          
            <span class="post-meta-divider">|</span>
            <span class="page-pv"><i class="fa fa-file-o"></i> 阅读量
            <span class="busuanzi-value" id="busuanzi_value_page_pv" ></span>
            </span>
          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  17.9k 字
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  75 分钟
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p><strong><a href="https://github.com/Normcorer/MinaDemo.git" target="_blank" rel="noopener">Mina案例源码</a></strong></p>
<h2 id="简单的TCPServer"><a href="#简单的TCPServer" class="headerlink" title="简单的TCPServer"></a>简单的TCPServer</h2><h3 id="第一步：编写IoService"><a href="#第一步：编写IoService" class="headerlink" title="第一步：编写IoService"></a>第一步：编写IoService</h3><p>编写IoService，按照上面的执行流程，我们首先需要编写IoService，IoService 本身既是服务端，又是客户端，我们这里编写服务端，所以使用IoAcceptor 实现，由于IoAcceptor 是与协议无关的，因为我们要编写TCPServer，所以我们使用IoAcceptor 的实现NioSocketAcceptor，实际上底层就是调用java.nio.channels.ServerSocketChannel 类。当然，如果你使用了Apache 的APR 库，那么你可以选择使AprSocketAcceptor 作为TCPServer 的实现，据传说Apache APR库的性能比JVM 自带的本地库高出很多。那么IoProcessor 是由指定的IoService 内部创建并调用的，我们并不需要关心。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">IoAcceptor acceptor &#x3D; new NioSocketAcceptor();</span><br><span class="line">        acceptor.getSessionConfig().setReadBufferSize(2048);</span><br><span class="line">        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);</span><br><span class="line">        </span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F;设置过滤器</span><br><span class="line">&#x2F;&#x2F;...</span><br><span class="line">&#x2F;&#x2F;设置handler</span><br><span class="line">&#x2F;&#x2F;绑定端口</span><br><span class="line">acceptor.bind(new InetSocketAddress(9124));</span><br></pre></td></tr></table></figure>
<p>这段代码我们初始化了服务端的TCP/IP 的基于NIO 的套接字，然后调用IoSessionConfig设置读取数据的缓冲区大小、读写通道均在10 秒内无任何操作就进入空闲状态。</p>
<h3 id="第二步：编写过滤器"><a href="#第二步：编写过滤器" class="headerlink" title="第二步：编写过滤器"></a>第二步：编写过滤器</h3><p>这里我们处理最简单的字符串传输，Mina 已经为我们提供了TextLineCodecFactory 编解码器工厂来对字符串进行编解码处理</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 编写过滤器</span><br><span class="line">acceptor.getFilterChain().addLast(&quot;codec&quot;,</span><br><span class="line">        new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName(&quot;UTF-8&quot;),</span><br><span class="line">        LineDelimiter.WINDOWS.getValue(), </span><br><span class="line">        LineDelimiter.WINDOWS.getValue())));</span><br></pre></td></tr></table></figure>
<p>这段代码要在acceptor.bind()方法之前执行，因为绑定套接字之后就不能再做这些准备工作了。这里先不用清楚编解码器是如何工作的，这个是后面重点说明的内容，这里你只需要清楚，我们传输的以换行符为标识的数据，所以使用了Mina 自带的换行符编解码器工厂。</p>
<h3 id="第三步：编写IoHandler"><a href="#第三步：编写IoHandler" class="headerlink" title="第三步：编写IoHandler"></a>第三步：编写IoHandler</h3><p>这里我们只是简单的打印Client 传说过来的数据。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoHandlerAdapter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPServerHandler</span> <span class="keyword">extends</span> <span class="title">IoHandlerAdapter</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 这里我们使用的SLF4J作为日志门面，至于为什么在后面说明。</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> Logger log = LoggerFactory.getLogger(TCPServerHandler<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">messageReceived</span><span class="params">(IoSession session, Object message)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        String str = message.toString();</span><br><span class="line">        System.out.println(<span class="string">"The message received is ["</span> + str + <span class="string">"]"</span>);</span><br><span class="line">        <span class="keyword">if</span> (str.endsWith(<span class="string">"quit"</span>)) &#123;</span><br><span class="line">            session.close(<span class="keyword">true</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionCreated</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"server session created"</span>);</span><br><span class="line">        <span class="keyword">super</span>.sessionCreated(session);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionOpened</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"server session Opened"</span>);</span><br><span class="line">        <span class="keyword">super</span>.sessionOpened(session);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionClosed</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"server session Closed"</span>);</span><br><span class="line">        <span class="keyword">super</span>.sessionClosed(session);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后我们把这个IoHandler 注册到IoService：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;设置handler</span><br><span class="line">acceptor.setHandler(new TCPServerHandler());</span><br></pre></td></tr></table></figure>
<p>当然这段代码也要在acceptor.bind()方法之前执行。然后我们运行MyServer 中的main 方法，你可以看到控制台一直处于阻塞状态，等待客户端连接。<br>完成的代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoAcceptor;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IdleStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.LineDelimiter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.TextLineCodecFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketAcceptor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPServer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        IoAcceptor acceptor = <span class="keyword">new</span> NioSocketAcceptor();</span><br><span class="line">        acceptor.getSessionConfig().setReadBufferSize(<span class="number">2048</span>);</span><br><span class="line">        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, <span class="number">10</span>);</span><br><span class="line">        </span><br><span class="line"></span><br><span class="line">        <span class="comment">// 编写过滤器</span></span><br><span class="line">        acceptor.getFilterChain().addLast(<span class="string">"codec"</span>,</span><br><span class="line">                <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> TextLineCodecFactory(Charset.forName(<span class="string">"UTF-8"</span>),</span><br><span class="line">                        LineDelimiter.WINDOWS.getValue(), </span><br><span class="line">                        LineDelimiter.WINDOWS.getValue()))</span><br><span class="line">                );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//设置handler</span></span><br><span class="line">        acceptor.setHandler(<span class="keyword">new</span> TCPServerHandler());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//绑定端口</span></span><br><span class="line">        acceptor.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">9124</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>测试：<br>此时，我们用telnet 127.0.0.1 9123 访问，然后输入一些内容，当按下回车键，你会发现数据在Server 端被输出，但要注意不要输入中文，因为Windows 的命令行窗口不会对传输的数据进行UTF-8 编码。当输入quit 结尾的字符串时，连接被断开。这里注意你如果使用的操作系统，或者使用的Telnet 软件的换行符是什么，如果不清楚，可以删掉第二步中的两个红色的参数，使用TextLineCodec 内部的自动识别机制。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- MINA集成 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.mina<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mina-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.0.7<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.mina<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mina-integration-spring<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.1.7<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h2 id="简单的TCPClient"><a href="#简单的TCPClient" class="headerlink" title="简单的TCPClient"></a>简单的TCPClient</h2><p>这里我们实现Mina 中的TCPClient，因为前面说过无论是Server 端还是Client 端，在Mina中的执行流程都是一样的。唯一不同的就是IoService 的Client 端实现是IoConnector。</p>
<h3 id="第一步编写IoService并注册过滤器"><a href="#第一步编写IoService并注册过滤器" class="headerlink" title="第一步编写IoService并注册过滤器"></a>第一步编写IoService并注册过滤器</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoConnector;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.LineDelimiter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.TextLineCodecFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketConnector;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPClient</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        IoConnector connector = <span class="keyword">new</span> NioSocketConnector();</span><br><span class="line">        connector.setConnectTimeoutMillis(<span class="number">30000</span>);</span><br><span class="line">        connector.getFilterChain().addLast(<span class="string">"codec"</span>,</span><br><span class="line">                <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> TextLineCodecFactory(Charset.forName(<span class="string">"UTF-8"</span>),</span><br><span class="line">                        LineDelimiter.WINDOWS.getValue(), </span><br><span class="line">                        LineDelimiter.WINDOWS.getValue())));</span><br><span class="line">        </span><br><span class="line">        connector.setHandler(<span class="keyword">new</span> TCPClientHandler(<span class="string">"你好！\r\n 大家好！"</span>));</span><br><span class="line">        connector.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"localhost"</span>, <span class="number">9124</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="第二步：编写IoHandler"><a href="#第二步：编写IoHandler" class="headerlink" title="第二步：编写IoHandler"></a>第二步：编写IoHandler</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo2;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoHandlerAdapter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPClientHandler</span> <span class="keyword">extends</span> <span class="title">IoHandlerAdapter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> Logger LOGGER = LoggerFactory.getLogger(TCPClientHandler<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String values;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TCPClientHandler</span><span class="params">(String values)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.values = values;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionOpened</span><span class="params">(IoSession session)</span> </span>&#123;</span><br><span class="line">        session.write(values);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>注册IoHandler：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">connector.setHandler(new ClientHandler(&quot;你好！\r\n 大家好！&quot;));</span><br></pre></td></tr></table></figure>
<p>然后我们运行MyClient，你会发现MyServer 输出如下语句：<br>The message received is [你好！]<br>The message received is [大家好！]<br>我们看到服务端是按照收到两条消息输出的，因为我们用的编解码器是以换行符判断数据是否读取完毕的。</p>
<h2 id="介绍Mina的TCP的主要接口"><a href="#介绍Mina的TCP的主要接口" class="headerlink" title="介绍Mina的TCP的主要接口"></a>介绍Mina的TCP的主要接口</h2><p>通过上面的两个示例，你应该对Mina 如何编写TCP/IP 协议栈的网络通信有了一些感性的认识。</p>
<ol>
<li><p>IoService：<br>这个接口是服务端IoAcceptor、客户端IoConnector 的抽象，提供IO 服务和管理IoSession的功能，它有如下几个常用的方法：</p>
<p> A. TransportMetadata getTransportMetadata()：<br>这个方法获取传输方式的元数据描述信息，也就是底层到底基于什么的实现，譬如：nio、apr 等。</p>
<p> B. void addListener(IoServiceListener listener)：<br>这个方法可以为IoService 增加一个监听器，用于监听IoService 的创建、活动、失效、空闲、销毁，具体可以参考IoServiceListener 接口中的方法，这为你参与IoService 的生命周期提供了机会。</p>
<p> C. void removeListener(IoServiceListener listener)：<br>这个方法用于移除上面的方法添加的监听器。</p>
<p> D. void setHandler(IoHandler handler)：<br>这个方法用于向IoService 注册IoHandler，同时有getHandler()方法获取Handler。</p>
<p> E. Map&lt;Long,IoSession&gt; getManagedSessions()：<br>这个方法获取IoService 上管理的所有IoSession，Map 的key 是IoSession 的id。</p>
<p> F. IoSessionConfig getSessionConfig()：<br>这个方法用于获取IoSession 的配置对象，通过IoSessionConfig 对象可以设置Socket 连接的一些选项。</p>
</li>
<li><p>IoAcceptor：<br>这个接口是TCPServer 的接口，主要增加了void bind()监听端口、void unbind()解除对套接字的监听等方法。这里与传统的JAVA 中的ServerSocket 不同的是IoAcceptor 可以多次调用bind()方法（或者在一个方法中传入多个SocketAddress 参数）同时监听多个端口。 </p>
</li>
<li><p>IoConnector：<br>这个接口是TCPClient 的接口,主要增加了ConnectFuture connect(SocketAddressremoteAddress,SocketAddress localAddress)方法，用于与Server 端建立连接，第二个参数如果不传递则使用本地的一个随机端口访问Server 端。这个方法是异步执行的，同样的，也可以同时连接多个服务端。</p>
</li>
<li><p>IoSession：<br>这个接口用于表示Server 端与Client 端的连接，IoAcceptor.accept()的时候返回实例。<br>这个接口有如下常用的方法：</p>
<p> A. WriteFuture write(Object message)：<br>这个方法用于写数据，该操作是异步的。</p>
<p> B. CloseFuture close(boolean immediately)：<br>这个方法用于关闭IoSession，该操作也是异步的，参数指定true 表示立即关闭，否则就在所有的写操作都flush 之后再关闭。</p>
<p> C. Object setAttribute(Object key,Object value)：<br>这个方法用于给我们向会话中添加一些属性，这样可以在会话过程中都可以使用，类似于HttpSession 的setAttrbute()方法。IoSession 内部使用同步的HashMap 存储你添加的自定义属性</p>
<p> D. SocketAddress getRemoteAddress()：<br>这个方法获取远端连接的套接字地址。</p>
<p> E. void suspendWrite()：<br>这个方法用于挂起写操作，那么有void resumeWrite()方法与之配对。对于read()方法同样适用。</p>
<p> F. ReadFuture read()：<br>这个方法用于读取数据， 但默认是不能使用的， 你需要调用IoSessionConfig 的setUseReadOperation(true)才可以使用这个异步读取的方法。一般我们不会用到这个方法，因为这个方法的内部实现是将数据保存到一个BlockingQueue，假如是Server 端，因为大量的Client 端发送的数据在Server 端都这么读取，那么可能会导致内存泄漏，但对于Client，可能有的时候会比较便利。</p>
<p> G. IoService getService()：<br>这个方法返回与当前会话对象关联的IoService 实例。<br>关于TCP连接的关闭：<br>无论在客户端还是服务端，IoSession 都用于表示底层的一个TCP 连接，那么你会发现无论是Server 端还是Client 端的IoSession 调用close()方法之后，TCP 连接虽然显示关闭， 但主线程仍然在运行，也就是JVM 并未退出，这是因为IoSession 的close()仅仅是关闭了TCP的连接通道，并没有关闭Server 端、Client 端的程序。你需要调用IoService 的dispose()方法停止Server 端、Client 端。</p>
</li>
<li><p>IoSessionConfig：<br>这个方法用于指定此次会话的配置，它有如下常用的方法：</p>
<p> A. void setReadBufferSize(int size)：<br>这个方法设置读取缓冲的字节数，但一般不需要调用这个方法，因为IoProcessor 会自动调整缓冲的大小。你可以调用setMinReadBufferSize()、setMaxReadBufferSize()方法，这样无论IoProcessor 无论如何自动调整，都会在你指定的区间。</p>
<p> B. void setIdleTime(IdleStatus status,int idleTime)：<br>这个方法设置关联在通道上的读、写或者是读写事件在指定时间内未发生，该通道就进入空闲状态。一旦调用这个方法，则每隔idleTime 都会回调过滤器、IoHandler 中的sessionIdle()方法。</p>
<p> C. void setWriteTimeout(int time)：<br>这个方法设置写操作的超时时间</p>
<p> D. void setUseReadOperation(boolean useReadOperation)：<br>这个方法设置IoSession 的read()方法是否可用，默认是false。</p>
</li>
<li><p>IoHandler：<br>这个接口是你编写业务逻辑的地方，从上面的示例代码可以看出，读取数据、发送数据基本都在这个接口总完成，这个实例是绑定到IoService 上的，有且只有一个实例（没有给一个IoService 注入一个IoHandler 实例会抛出异常）。它有如下几个方法：</p>
<p> A. void sessionCreated(IoSession session)：<br>这个方法当一个Session 对象被创建的时候被调用。对于TCP 连接来说，连接被接受的时候调用，但要注意此时TCP 连接并未建立，此方法仅代表字面含义，也就是连接的对象IoSession 被创建完毕的时候，回调这个方法。对于UDP 来说，当有数据包收到的时候回调这个方法，因为UDP 是无连接的。</p>
<p> B. void sessionOpened(IoSession session)：<br>这个方法在连接被打开时调用，它总是在sessionCreated()方法之后被调用。对于TCP 来说，它是在连接被建立之后调用，你可以在这里执行一些认证操作、发送数据等。对于UDP 来说，这个方法与sessionCreated()没什么区别，但是紧跟其后执行。如果你每隔一段时间，发送一些数据，那么sessionCreated()方法只会在第一次调用，但是sessionOpened()方法每次都会调用。</p>
<p> C. void sessionClosed(IoSession session) ：<br>对于TCP 来说，连接被关闭时，调用这个方法。对于UDP 来说，IoSession 的close()方法被调用时才会毁掉这个方法。</p>
<p> D. void sessionIdle(IoSession session, IdleStatus status) ：<br>这个方法在IoSession 的通道进入空闲状态时调用，对于UDP 协议来说，这个方法始终不会被调用。</p>
<p> E. void exceptionCaught(IoSession session, Throwable cause) ：<br>这个方法在你的程序、Mina 自身出现异常时回调，一般这里是关闭IoSession。</p>
<p> F. void messageReceived(IoSession session, Object message) ：<br>接收到消息时调用的方法，也就是用于接收消息的方法，一般情况下，message 是一个IoBuffer 类，如果你使用了协议编解码器，那么可以强制转换为你需要的类型。通常我们都是会使用协议编解码器的， 就像上面的例子， 因为协议编解码器是<br>TextLineCodecFactory，所以我们可以强制转message 为String 类型。</p>
<p> G. void messageSent(IoSession session, Object message) ：<br>当发送消息成功时调用这个方法，注意这里的措辞，发送成功之后，也就是说发送消息是不能用这个方法的。<br>发送消息的时机：<br>发送消息应该在sessionOpened()、messageReceived()方法中调用IoSession.write()方法完成。因为在sessionOpened()方法中，TCP 连接已经真正打开，同样的在messageReceived()方法TCP 连接也是打开状态，只不过两者的时机不同。sessionOpened()方法是在TCP 连接建立之后，接收到数据之前发送；messageReceived()方法是在接收到数据之后发送，你可以完成依据收到的内容是什么样子，决定发送什么样的数据。因为这个接口中的方法太多，因此通常使用适配器模式IoHandlerAdapter，覆盖你所感兴趣的方法即可。</p>
</li>
<li><p>IoBuffer：<br>这个接口是对JAVA NIO 的ByteBuffer 的封装，这主要是因为ByteBuffer 只提供了对基本数据类型的读写操作，没有提供对字符串等对象类型的读写方法，使用起来更为方便，另外，ByteBuffer 是定长的，如果想要可变，将很麻烦。IoBuffer 的可变长度的实现类似于StringBuffer。IoBuffer 与ByteBuffer 一样，都是非线程安全的。本节的一些内容如果不清楚，可以参考java.nio.ByteBuffer 接口。这个接口有如下常用的方法：</p>
<p> A. static IoBuffer allocate(int capacity,boolean useDirectBuffer)：<br>这个方法内部通过SimpleBufferAllocator 创建一个实例，第一个参数指定初始化容量，第二个参数指定使用直接缓冲区还是JAVA 内存堆的缓存区，默认为false。</p>
<p> B. void free()：<br>释放缓冲区，以便被一些IoBufferAllocator 的实现重用，一般没有必要调用这个方法，除非你想提升性能（但可能未必效果明显。</p>
<p> C. IoBuffer setAutoExpand(boolean autoExpand)：<br>这个方法设置IoBuffer 为自动扩展容量，也就是前面所说的长度可变，那么可以看出长度可变这个特性默认是不开启的。</p>
<p> D. IoBuffer setAutoShrink(boolean autoShrink)：<br>这个方法设置IoBuffer 为自动收缩，这样在compact()方法调用之后，可以裁减掉一些没有使用的空间。如果这个方法没有被调用或者设置为false，你也可以通过调用shrink()方法手动收缩空间。</p>
<p> E. IoBuffer order(ByteOrder bo)：<br>这个方法设置是Big Endian 还是Little Endian，JAVA 中默认是Big Endian，C++和其他语言一般是Little Endian。</p>
<p> F. IoBuffer asReadOnlyBuffer()：<br>这个方法设置IoBuffer 为只读的。</p>
<p> G. Boolean prefixedDataAvailable(int prefixLength,int maxDataLength)：<br>这个方法用于数据的最开始的1、2、4 个字节表示的是数据的长度的情况，<br>prefixLentgh表示这段数据的前几个字节（只能是1、2、4 的其中一个），代表的是这段数据的长度，<br>maxDataLength 表示最多要读取的字节数。返回结果依赖于等式<br>remaining()-prefixLength&gt;=maxDataLength，也就是总的数据-表示长度的字节，剩下的字节数要比打算读取的字节数大或者相等。</p>
<p> H. String getPrefixedString(int prefixLength,CharsetDecoder decoder)：<br>如果上面的方法返回true，那么这个方法将开始读取表示长度的字节之后的数据，注意要保持这两个方法的prefixLength 的值是一样的。</p>
<p> G、H 两个方法在后面讲到的PrefixedStringDecoder 中的内部实现使用。<br>IoBuffer 剩余的方法与ByteBuffer 都是差不多的，额外增加了一些便利的操作方法，例如：<br>IoBuffer putString(String value,CharsetEncoder encoder)可以方便的以指定的编码方式存储字符串、InputStream asInputStream()方法从IoBuffer 剩余的未读的数据中转为输入流等。</p>
</li>
<li><p>IoFuture：<br>在Mina 的很多操作中，你会看到返回值是XXXFuture，实际上他们都是IoFuture 的子类，看到这样的返回值，这个方法就说明是异步执行的，主要的子类有ConnectFuture、CloseFuture 、ReadFuture 、WriteFuture 。这个接口的大部分操作都和<br>java.util.concurrent.Future 接口是类似的，譬如：await()、awaitUninterruptibly()等，一般我们常用awaitUninterruptibly()方法可以等待异步执行的结果返回。这个接口有如下常用的方法：</p>
<p> A. IoFuture addListener(IoFutureListener<?> listener)：
`这个方法用于添加一个监听器， 在异步执行的结果返回时监听器中的回调方法operationComplete(IoFuture future)，也就是说，这是替代awaitUninterruptibly()方法另一种等待异步执行结果的方法，它的好处是不会产生阻塞。
`
 B. IoFuture removeListener(IoFutureListener<?> listener)：<br>这个方法用于移除指定的监听器。</p>
<p> C. IoSession getSession()：<br>这个方法返回当前的IoSession。举个例子，我们在客户端调用connect()方法访问Server 端的时候，实际上这就是一个异步执行的方法，也就是调用connect()方法之后立即返回，执行下面的代码，而不管是否连接成功。那么如果我想在连接成功之后执行一些事情（譬如：获取连接成功后的IoSession对象），该怎么办呢？按照上面的说明，你有如下两种办法：</p>
</li>
</ol>
<ul>
<li>第一种 <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.future.ConnectFuture;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoConnector;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.LineDelimiter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.TextLineCodecFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketConnector;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.dxz.minademo2.TCPClientHandler;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPClient</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        IoConnector connector = <span class="keyword">new</span> NioSocketConnector();</span><br><span class="line">        connector.setConnectTimeoutMillis(<span class="number">30000</span>);</span><br><span class="line">        connector.getFilterChain().addLast(<span class="string">"codec"</span>,</span><br><span class="line">                <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> TextLineCodecFactory(Charset.forName(<span class="string">"UTF-8"</span>),</span><br><span class="line">                        LineDelimiter.WINDOWS.getValue(), </span><br><span class="line">                        LineDelimiter.WINDOWS.getValue())));</span><br><span class="line">        </span><br><span class="line">        connector.setHandler(<span class="keyword">new</span> TCPClientHandler(<span class="string">"你好！\r\n 大家好！"</span>));    </span><br><span class="line">        </span><br><span class="line">        ConnectFuture future = connector.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"localhost"</span>, <span class="number">9124</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 等待是否连接成功，相当于是转异步执行为同步执行。    </span></span><br><span class="line">        future.awaitUninterruptibly();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 连接成功后获取会话对象。如果没有上面的等待，由于connect()方法是异步的，session可能会无法获取。    </span></span><br><span class="line">        IoSession session = future.getSession(); </span><br><span class="line">        System.out.println(session);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>第二种 <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.future.ConnectFuture;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.future.IoFutureListener;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoConnector;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.LineDelimiter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.TextLineCodecFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketConnector;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.dxz.minademo2.TCPClientHandler;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPClient</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        IoConnector connector = <span class="keyword">new</span> NioSocketConnector();</span><br><span class="line">        connector.setConnectTimeoutMillis(<span class="number">30000</span>);</span><br><span class="line">        connector.getFilterChain().addLast(<span class="string">"codec"</span>,</span><br><span class="line">                <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> TextLineCodecFactory(Charset.forName(<span class="string">"UTF-8"</span>),</span><br><span class="line">                        LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue())));</span><br><span class="line"></span><br><span class="line">        connector.setHandler(<span class="keyword">new</span> TCPClientHandler(<span class="string">"你好！\r\n 大家好！"</span>));</span><br><span class="line">        ConnectFuture future = connector.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"localhost"</span>, <span class="number">9124</span>));</span><br><span class="line">        future.addListener(<span class="keyword">new</span> IoFutureListener&lt;ConnectFuture&gt;() &#123;</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">operationComplete</span><span class="params">(ConnectFuture future)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                IoSession session = future.getSession();</span><br><span class="line">                System.out.println(<span class="string">"++++++++++++++++++++++++++++"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        System.out.println(<span class="string">"*************"</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>结果<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">*************</span><br><span class="line">++++++++++++++++++++++++++++</span><br></pre></td></tr></table></figure>

</li>
</ul>
<h2 id="日志配置"><a href="#日志配置" class="headerlink" title="日志配置"></a>日志配置</h2><p>前面的示例代码中提到了使用SLF4J 作为日志门面，这是<code>因为Mina 内部使用的就是SLF4J</code>，你也使用SLF4J 可以与之保持一致性。Mina 如果想启用日志跟踪Mina 的运行细节，你可以配置LoggingFilter 过滤器，这样你可以看到Session 建立、打开、空闲等一系列细节在日志中输出，默认SJF4J 是按照DEBUG级别输出跟踪信息的，如果你想给某一类别的Mina 运行信息输出指定日志输出级别，可以调用LoggingFilter 的setXXXLogLevel(LogLevel.XXX)。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">LoggingFilter lf &#x3D; new LoggingFilter();    </span><br><span class="line">lf.setSessionOpenedLogLevel(LogLevel.ERROR);    </span><br><span class="line">acceptor.getFilterChain().addLast(&quot;logger&quot;, lf);</span><br></pre></td></tr></table></figure>
<p>这里IoSession 被打开的跟踪信息将以ERROR 级别输出到日志。</p>
<h2 id="过滤器"><a href="#过滤器" class="headerlink" title="过滤器"></a>过滤器</h2><p>前面我们看到了LoggingFilter、ProtocolCodecFilter 两个过滤器，一个负责日志输出，一个负责数据的编解码，通过最前面的Mina 执行流程图，在IoProcessor 与IoHandler 之间可以有很多的过滤器，这种设计方式为你提供可插拔似的扩展功能提供了非常便利的方式，目前的Apache CXF、Apache Struts2 中的拦截器也都是一样的设计思路。Mina 中的IoFilter 是单例的，这与CXF、Apache Struts2 没什么区别。IoService 实例上会绑定一个DefaultIoFilterChainBuilder 实例，DefaultIoFilterChainBuilder 会把使用内部的EntryImpl 类把所有的过滤器按照顺序连在一起，组成一个过滤器链。<br>DefaultIoFilterChainBuilder 类如下常用的方法：</p>
<p>A. void addFirst(String name,IoFilter filter)：<br>这个方法把过滤器添加到过滤器链的头部，头部就是IoProcessor 之后的第一个过滤器。同样的addLast()方法把过滤器添加到过滤器链的尾部。</p>
<p>B. void addBefore(String baseName,String name,IoFilter filter)：<br>这个方法将过滤器添加到baseName 指定的过滤器的前面，同样的addAfter()方法把过滤器添加到baseName 指定的过滤器的后面。这里要注意无论是那种添加方法，每个过滤器的名字（参数name）必须是唯一的。</p>
<p>C. IoFilter remove(Stirng name)：<br>这个方法移除指定名称的过滤器，你也可以调用另一个重载的remove()方法，指定要移除的IoFilter 的类型。</p>
<p>D. List<Entry> getAll()：<br>这个方法返回当前IoService 上注册的所有过滤器。默认情况下，过滤器链中是空的，也就是getAll()方法返回长度为0 的List，但实际Mina内部有两个隐藏的过滤器：HeadFilter、TailFilter，分别在List 的最开始和最末端，很明显，TailFilter 在最末端是为了调用过滤器链之后，调用IoHandler。但这两个过滤器对你来说是透明的，可以忽略它们的存在。编写一个过滤器很简单，你需要实现IoFilter 接口，如果你只关注某几个方法，可以继承IoFilterAdapter 适配器类。IoFilter 接口中主要包含两类方法，一类是与IoHandler 中的方法名一致的方法，相当于拦截IoHandler 中的方法，另一类是IoFilter 的生命周期回调方法，这些回调方法的执行顺序和解释如下所示：</p>
<ol>
<li>init()在首次添加到链中的时候被调用，但你必须将这个IoFilter 用<br>ReferenceCountingFilter 包装起来，否则init()方法永远不会被调用。</li>
<li>onPreAdd()在调用添加到链中的方法时被调用，但此时还未真正的加入到链。</li>
<li>onPostAdd()在调用添加到链中的方法后被调，如果在这个方法中有异常抛出，则过滤器会立即被移除，同时destroy()方法也会被调用（前提是使用ReferenceCountingFilter包装）。</li>
<li>onPreRemove()在从链中移除之前调用。</li>
<li>onPostRemove()在从链中移除之后调用。</li>
<li>destory()在从链中移除时被调用，使用方法与init()要求相同。<br>无论是哪个方法，要注意必须在实现时调用参数nextFilter 的同名方法，否则，过滤器链的执行将被中断，IoHandler 中的同名方法一样也不会被执行，这就相当于Servlet 中的Filter 必须调用filterChain.doFilter(request,response)才能继续前进是一样的道理。<br>示例：<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.filterchain.IoFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.filterchain.IoFilterChain;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IdleStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.write.WriteRequest;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyIoFilter</span> <span class="keyword">implements</span> <span class="title">IoFilter</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">destroy</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%�stroy"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">exceptionCaught</span><span class="params">(NextFilter nextFilter, IoSession session, Throwable cause)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%exceptionCaught"</span>);</span><br><span class="line">        nextFilter.exceptionCaught(session, cause);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">filterClose</span><span class="params">(NextFilter nextFilter, IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%filterClose"</span>);</span><br><span class="line">        nextFilter.filterClose(session);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">filterWrite</span><span class="params">(NextFilter nextFilter, IoSession session, WriteRequest writeRequest)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%filterWrite"</span>);</span><br><span class="line">        nextFilter.filterWrite(session, writeRequest);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%init"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">messageReceived</span><span class="params">(NextFilter nextFilter, IoSession session, Object message)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%messageReceived"</span>);</span><br><span class="line">        nextFilter.messageReceived(session, message);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">messageSent</span><span class="params">(NextFilter nextFilter, IoSession session, WriteRequest writeRequest)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%messageSent"</span>);</span><br><span class="line">        nextFilter.messageSent(session, writeRequest);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPostAdd</span><span class="params">(IoFilterChain parent, String name, NextFilter nextFilter)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%onPostAdd"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPostRemove</span><span class="params">(IoFilterChain parent, String name, NextFilter nextFilter)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%onPostRemove"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPreAdd</span><span class="params">(IoFilterChain parent, String name, NextFilter nextFilter)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%onPreAdd"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPreRemove</span><span class="params">(IoFilterChain parent, String name, NextFilter nextFilter)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%onPreRemove"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionClosed</span><span class="params">(NextFilter nextFilter, IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%sessionClosed"</span>);</span><br><span class="line">        nextFilter.sessionClosed(session);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionCreated</span><span class="params">(NextFilter nextFilter, IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%sessionCreated"</span>);</span><br><span class="line">        nextFilter.sessionCreated(session);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionIdle</span><span class="params">(NextFilter nextFilter, IoSession session, IdleStatus status)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%sessionIdle"</span>);</span><br><span class="line">        nextFilter.sessionIdle(session, status);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionOpened</span><span class="params">(NextFilter nextFilter, IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"%%%%%%%%%%%%%%%%%%%%%%%%%%%sessionOpened"</span>);</span><br><span class="line">        nextFilter.sessionOpened(session);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
我们将这个拦截器注册到上面的TCPServer 的IoAcceptor 的过滤器链中的最后一个：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">acceptor.getFilterChain().addLast(&quot;myIoFilter&quot;, new ReferenceCountingFilter(new MyIoFilter()));</span><br></pre></td></tr></table></figure>
这里我们将MyIoFilter 用ReferenceCountingFilter 包装起来，这样你可以看到init()、destroy()方法调用。我们启动客户端访问，然后关闭客户端，你会看到执行顺序如下所示：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%init</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%onPreAdd</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%onPostAdd</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%sessionCreated</span><br><span class="line">server session created</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%sessionOpened</span><br><span class="line">server session Opened</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%messageReceived</span><br><span class="line">The message received is [你好！]</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%messageReceived</span><br><span class="line">The message received is [ 大家好！]</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%sessionIdle</span><br></pre></td></tr></table></figure>
IoHandler 的对应方法会跟在上面的对应方法之后执行，这也就是说从横向（单独的看一个过滤器中的所有方法的执行顺序）上看，每个过滤器的执行顺序是上面所示的顺序；从纵向（方法链的调用）上看，如果有filter1、filter2 两个过滤器，sessionCreated()方法的执行顺序如下所示：</li>
</ol>
<p>filter1-sessionCreated filter2-sessionCreated IoHandler-sessionCreated。<br>这里你要注意init、onPreAdd、onPostAdd 三个方法并不是在Server 启动时调用的，而是IoSession 对象创建之前调用的，也就是说IoFilterChain.addXXX()方法仅仅负责初始化过滤器并注册过滤器，但并不调用任何方法，包括init()初始化方法也是在IoProcessor 开始工作的时候被调用。IoFilter 是单例的，那么init()方法是否只被执行一次呢？这个是不一定的，因为IoFilter是被IoProcessor 调用的，而每个IoService 通常是关联多个IoProcessor，所以IoFilter的init()方法是在每个IoProcessor 线程上只执行一次。关于Mina 的线程问题，我们后面会详细讨论，这里你只需要清楚，init()与destroy()的调用次数与IoProceesor 的个数有关，假如一个IoService 关联了3 个IoProcessor，有五个并发的客户端请求，那么你会看到三次init()方法被调用，以后将不再会调用。Mina中自带的过滤器：<br>过滤器 说明<br>BlacklistFilter 设置一些IP 地址为黑名单，不允许访问。<br>BufferedWriteFilter 设置输出时像BufferedOutputStream 一样进行缓冲。<br>CompressionFilter 设置在输入、输出流时启用JZlib 压缩。<br>ConnectionThrottleFilter 这个过滤器指定同一个IP 地址（不含端口号）上的请求在多长的毫秒值内可以有一个请求，如果小于指定的时间间隔就有连续两个请求，那么第二个请求将被忽略（IoSession.close()）。正如Throttle 的名字一样，调节访问的频率这个过滤器最好放在过滤器链的前面。<br>FileRegionWriteFilter 如果你想使用File 对象进行输出，请使用这个过滤器。要注意，你需要使用WriteFuture 或者在<br>messageSent() 方法中关闭File 所关联的FileChannel 通道。<br>StreamWriteFilter 如果你想使用InputStream 对象进行输出，请使用这个过滤器。要注意，你需要使用WriteFuture或者在messageSent()方法中关闭File 所关联的<br>FileChannel 通道。NoopFilter 这个过滤器什么也不做，如果你想测试过滤器链是否起作用，可以用它来测试。<br>ProfilerTimerFilter 这个过滤器用于检测每个事件方法执行的时间，所以最好放在过滤器链的前面。<br>ProxyFilter 这个过滤器在客户端使用ProxyConnector 作为实现时，会自动加入到过滤器链中，用于完成代理功能。<br>RequestResponseFilter 暂不知晓。</p>
<p>SessionAttributeInitializingFilter 这个过滤器在IoSession 中放入一些属性（Map），通常放在过滤器的前面，用于放置一些初始化的信息。<br>MdcInjectionFilter 针对日志输出做MDC 操作，可以参考LOG4J 的MDC、NDC 的文档。<br>WriteRequestFilter CompressionFilter、RequestResponseFilter 的基类，用于包装写请求的过滤器。<br>还有一些过滤器，会在各节中详细讨论，这里没有列出，譬如：前面的LoggingFilger 日志过滤器。</p>
<h2 id="协议编解码器"><a href="#协议编解码器" class="headerlink" title="协议编解码器"></a>协议编解码器</h2><p>multiplex：英 [ˈmʌltɪpleks] 美 [ˈmʌltəˌplɛks] adj.多元的，多倍的，复式的;多部的，复合的，多样的，多重的;；[电讯]多路传输的n.多路;多厅影院，多剧场影剧院v.多路传输，多路复用;多重发讯<br><img src="https://molzhao-pic.oss-cn-beijing.aliyuncs.com/2020-06-20/%E5%8D%8F%E8%AE%AE%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8.png" alt="协议编解码器"></p>
<p>前面说过，协议编解码器是在使用Mina 的时候你最需要关注的对象，因为在网络传输的数据都是二进制数据（byte），而你在程序中面向的是JAVA 对象，这就需要你实现在发送数据时将JAVA 对象编码二进制数据，而接收数据时将二进制数据解码为JAVA 对象（这个可不是JAVA 对象的序列化、反序列化那么简单的事情）。Mina 中的协议编解码器通过过滤器ProtocolCodecFilter 构造，这个过滤器的构造方法需要一个ProtocolCodecFactory，这从前面注册TextLineCodecFactory 的代码就可以看出来。<br>ProtocolCodecFactory 中有如下两个方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ProtocolCodecFactory</span> </span>&#123;</span><br><span class="line">    <span class="function">ProtocolEncoder <span class="title">getEncoder</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">    <span class="function">ProtocolDecoder <span class="title">getDecoder</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>因此，构建一个ProtocolCodecFactory 需要ProtocolEncoder、ProtocolDecoder 两个实例。你可能要问JAVA 对象和二进制数据之间如何转换呢？这个要依据具体的通信协议，也就是Server 端要和Client 端约定网络传输的数据是什么样的格式，譬如：第一个字节表示数据长度，第二个字节是数据类型，后面的就是真正的数据（有可能是文字、有可能是图片等等），然后你可以依据长度从第三个字节向后读，直到读取到指定第一个字节指定长度的数据。<br>简单的说，HTTP 协议就是一种浏览器与Web 服务器之间约定好的通信协议，双方按照指定的协议编解码数据。我们再直观一点儿说，前面一直使用的TextLine 编解码器就是在读取网络上传递过来的数据时，只要发现哪个字节里存放的是ASCII 的10、13 字符（/r、/n），就认为之前的字节就是一个字符串（默认使用UTF-8 编码）。以上所说的就是各种协议实际上就是网络七层结构中的应用层协议，它位于网络层（IP）、传输层（TCP）之上，Mina 的协议编解码器就是让你实现一套自己的应用层协议栈。</p>
<h3 id="简单编解码器示例"><a href="#简单编解码器示例" class="headerlink" title="简单编解码器示例"></a>简单编解码器示例</h3><p>下面我们举一个模拟电信运营商短信协议的编解码器实现，假设通信协议如下所示：<br>M sip:wap.fetion.com.cn SIP-C/2.0<br>S: 1580101xxxx<br>R: 1889020xxxx</p>
<p>L: 21<br>Hello World!<br>这里的第一行表示状态行，一般表示协议的名字、版本号等，第二行表示短信的发送号码，第三行表示短信接收的号码，第四行表示短信的字节数，最后的内容就是短信的内容。上面的每一行的末尾使用ASC II 的10（/n）作为换行符，因为这是纯文本数据，协议要<br>求双方使用UTF-8 对字符串编解码。实际上如果你熟悉HTTP 协议，上面的这个精简的短信协议和HTTP 协议的组成是非常像的，第一行是状态行，中间的是消息报头，最后面的是消息正文。在解析这个短信协议之前，你需要知晓TCP 的一个事项，那就是数据的发送没有规模性，所谓的规模性就是作为数据的接收端，不知道到底什么时候数据算是读取完毕，所以应用层协议在制定的时候，必须指定数据读取的截至点。一般来说，有如下三种方式设置数据读取的长度：</p>
<ol>
<li>使用分隔符，譬如：TextLine 编解码器。你可以使用/r、/n、NUL 这些ASC II 中的特殊的字符来告诉数据接收端，你只要遇见分隔符，就表示数据读完了，不用在那里傻等着不知道还有没有数据没读完啊？我可不可以开始把已经读取到的字节解码为指定的数据类型了啊？</li>
<li>定长的字节数，这种方式是使用长度固定的数据发送，一般适用于指令发送，譬如：数据发送端规定发送的数据都是双字节，AA 表示启动、BB 表示关闭等等。</li>
<li>在数据中的某个位置使用一个长度域，表示数据的长度，这种处理方式最为灵活，上面的短信协议中的那个L 就是短信文字的字节数，其实HTTP 协议的消息报头中的Content-Length 也是表示消息正文的长度，这样数据的接收端就知道我到底读到多长的<br>字节数就表示不用再读取数据了。相比较解码（字节转为JAVA 对象，也叫做拆包）来说，编码（JAVA 对象转为字节，也叫做打包）就很简单了，你只需要把JAVA 对象转为指定格式的字节流，write()就可以了。下面我们开始对上面的短信协议进行编解码处理。</li>
</ol>
<ul>
<li>第一步，协议对象：<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SmsObject</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String sender;<span class="comment">// 短信发送者</span></span><br><span class="line">    <span class="keyword">private</span> String receiver;<span class="comment">// 短信接受者</span></span><br><span class="line">    <span class="keyword">private</span> String message;<span class="comment">// 短信内容</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getSender</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> sender;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setSender</span><span class="params">(String sender)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.sender = sender;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getReceiver</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> receiver;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setReceiver</span><span class="params">(String receiver)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.receiver = receiver;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getMessage</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setMessage</span><span class="params">(String message)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.message = message;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>第二步，编码器：<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.CharsetEncoder;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolEncoderAdapter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolEncoderOutput;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CmccSipcEncoder</span> <span class="keyword">extends</span> <span class="title">ProtocolEncoderAdapter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Charset charset;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CmccSipcEncoder</span><span class="params">(Charset charset)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.charset = charset;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">encode</span><span class="params">(IoSession session, Object message, ProtocolEncoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        SmsObject sms = (SmsObject) message;</span><br><span class="line">        CharsetEncoder ce = charset.newEncoder();</span><br><span class="line">        IoBuffer buffer = IoBuffer.allocate(<span class="number">100</span>).setAutoExpand(<span class="keyword">true</span>);</span><br><span class="line">        String statusLine = <span class="string">"M sip:wap.fetion.com.cn SIP-C/2.0"</span>;</span><br><span class="line">        String sender = sms.getSender();</span><br><span class="line">        String receiver = sms.getReceiver();</span><br><span class="line">        String smsContent = sms.getMessage();</span><br><span class="line">        buffer.putString(statusLine + <span class="string">'\n'</span>, ce);</span><br><span class="line">        buffer.putString(<span class="string">"S: "</span> + sender + <span class="string">'\n'</span>, ce);</span><br><span class="line">        buffer.putString(<span class="string">"R: "</span> + receiver + <span class="string">'\n'</span>, ce);</span><br><span class="line">        buffer.putString(<span class="string">"L: "</span> + (smsContent.getBytes(charset).length) + <span class="string">"\n"</span>, ce);</span><br><span class="line">        buffer.putString(smsContent, ce);</span><br><span class="line">        buffer.flip();</span><br><span class="line">        out.write(buffer);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
这里我们依据传入的字符集类型对message 对象进行编码，编码的方式就是按照短信协议拼装字符串到IoBuffer 缓冲区，然后调用ProtocolEncoderOutput 的write()方法输出字节流。这里要注意生成短信内容长度时的红色代码，我们使用String 类与Byte[]类型之间的转换方法获得转为字节流后的字节数。<br>编码器的编写有以下几个步骤：</li>
</ul>
<ol>
<li>将 encode()方法中的message 对象强制转换为指定的对象类型；</li>
<li>创建IoBuffer 缓冲区对象，并设置为自动扩展；</li>
<li>将转换后的message 对象中的各个部分按照指定的应用层协议进行组装，并put()到IoBuffer 缓冲区；</li>
<li>当你组装数据完毕之后，调用flip()方法，为输出做好准备，切记在write()方法之前，要调用IoBuffer 的flip()方法，否则缓冲区的position 的后面是没有数据可以用来输出的，你必须调用flip()方法将position 移至0，limit 移至刚才的position。这个flip()方法的含义请参看java.nio.ByteBuffer。</li>
<li>最后调用ProtocolEncoderOutput 的write()方法输出IoBuffer 缓冲区实例。</li>
</ol>
<ul>
<li>第三步，解码器：<br>在Mina 中编写解码器，可以实现ProtocolDecoder 接口，其中有decode()、finishDecode()、dispose()三个方法。这里的finishDecode()方法可以用于处理在IoSession 关闭时剩余的未读取数据，一般这个方法并不会被使用到，除非协议中未定义任何标识数据什么时候截止的约定，譬如：Http 响应的Content-Length 未设定，那么在你认为读取完数据后，关闭TCP连接（IoSession 的关闭）后，就可以调用这个方法处理剩余的数据，当然你也可以忽略调剩余的数据。同样的，一般情况下，我们只需要继承适配器ProtocolDecoderAdapter，关注decode()方法即可。但前面说过解码器相对编码器来说，最麻烦的是数据发送过来的规模，以聊天室为例，一个TCP 连接建立之后，那么隔一段时间就会有聊天内容发送过来，也就是decode()方法会被往复调用，这样处理起来就会非常麻烦。<code>那么Mina 中幸好提供了CumulativeProtocolDecoder类，从名字上可以看出累积性的协议解码器</code>，也就是说只要有数据发送过来，这个类就会去读取数据，然后累积到内部的IoBuffer 缓冲区，但是具体的拆包（把累积到缓冲区的数据解码为JAVA 对象）交由子类的doDecode()方法完成，实际上CumulativeProtocolDecoder就是在decode()反复的调用暴漏给子类实现的doDecode()方法。<br>具体执行过程如下所示：</li>
</ul>
<ol>
<li>你的doDecode()方法返回true 时，CumulativeProtocolDecoder 的decode()方法会首先判断你是否在doDecode()方法中从内部的IoBuffer 缓冲区读取了数据，如果没有，则会抛出非法的状态异常，也就是你的doDecode()方法返回true 就表示你已经消费了本次数据（相当于聊天室中一个完整的消息已经读取完毕），进一步说，也就是此时你必须已经消费过内部的IoBuffer 缓冲区的数据（哪怕是消费了一个字节的数据）。如果验证过通过，那么CumulativeProtocolDecoder 会检查缓冲区内是否还有数据未读取，如果有就继续调用doDecode()方法，没有就停止对doDecode()方法的调用，直到有新的数据被缓冲。</li>
<li>当你的doDecode()方法返回false 时，CumulativeProtocolDecoder 会停止对doDecode()方法的调用，<code>但此时如果本次数据还有未读取完的，就将含有剩余数据的IoBuffer 缓冲区保存到IoSession 中，以便下一次数据到来时可以从IoSession 中提取合并。</code>如果发现本次数据全都读取完毕，则清空IoBuffer 缓冲区。简而言之，当你认为读取到的数据已经够解码了，那么就返回true，否则就返回false。<code>这个CumulativeProtocolDecoder 其实最重要的工作就是帮你完成了数据的累积，因为这个工作是很烦琐的。</code><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.CharsetDecoder;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.CumulativeProtocolDecoder;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolDecoderOutput;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CmccSipcDecoder</span> <span class="keyword">extends</span> <span class="title">CumulativeProtocolDecoder</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Charset charset;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CmccSipcDecoder</span><span class="params">(Charset charset)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.charset = charset;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">doDecode</span><span class="params">(IoSession session, IoBuffer in, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        IoBuffer buffer = IoBuffer.allocate(<span class="number">100</span>).setAutoExpand(<span class="keyword">true</span>);</span><br><span class="line">        CharsetDecoder cd = charset.newDecoder();</span><br><span class="line">        <span class="keyword">int</span> matchCount = <span class="number">0</span>;</span><br><span class="line">        String statusLine = <span class="string">""</span>, sender = <span class="string">""</span>, receiver = <span class="string">""</span>, length = <span class="string">""</span>, sms = <span class="string">""</span>;</span><br><span class="line">        <span class="keyword">int</span> i = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (in.hasRemaining()) &#123;</span><br><span class="line">            <span class="keyword">byte</span> b = in.get();</span><br><span class="line">            buffer.put(b);</span><br><span class="line">            <span class="keyword">if</span> (b == <span class="number">10</span> &amp;&amp; i &lt; <span class="number">5</span>) &#123;</span><br><span class="line">                matchCount++;</span><br><span class="line">                <span class="keyword">if</span> (i == <span class="number">1</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    statusLine = buffer.getString(matchCount, cd);</span><br><span class="line">                    statusLine = statusLine.substring(<span class="number">0</span>, statusLine.length() - <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (i == <span class="number">2</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    sender = buffer.getString(matchCount, cd);</span><br><span class="line">                    sender = sender.substring(<span class="number">0</span>, sender.length() - <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (i == <span class="number">3</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    receiver = buffer.getString(matchCount, cd);</span><br><span class="line">                    receiver = receiver.substring(<span class="number">0</span>, receiver.length() - <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (i == <span class="number">4</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    length = buffer.getString(matchCount, cd);</span><br><span class="line">                    length = length.substring(<span class="number">0</span>, length.length() - <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                &#125;</span><br><span class="line">                i++;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (i == <span class="number">5</span>) &#123;</span><br><span class="line">                matchCount++;</span><br><span class="line">                <span class="keyword">if</span> (matchCount == Long.parseLong(length.split(<span class="string">": "</span>)[<span class="number">1</span>])) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    sms = buffer.getString(matchCount, cd);</span><br><span class="line">                    i++;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                matchCount++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        SmsObject smsObject = <span class="keyword">new</span> SmsObject();</span><br><span class="line">        smsObject.setSender(sender.split(<span class="string">": "</span>)[<span class="number">1</span>]);</span><br><span class="line">        smsObject.setReceiver(receiver.split(<span class="string">": "</span>)[<span class="number">1</span>]);</span><br><span class="line">        smsObject.setMessage(sms);</span><br><span class="line">        out.write(smsObject);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
我们的这个短信协议解码器使用/n（ASCII 的10 字符）作为分解点，一个字节一个字节的读取，那么第一次发现/n 的字节位置之前的部分，必然就是短信协议的状态行，依次类推，你就可以解析出来发送者、接受者、短信内容长度。然后我们在解析短信内容时，使用获取到的长度进行读取。全部读取完毕之后， 然后构造SmsObject 短信对象， 使用ProtocolDecoderOutput 的write()方法输出，最后返回false，也就是本次数据全部读取完毕，告知CumulativeProtocolDecoder 在本次数据读取中不需要再调用doDecode()方法了。这里需要注意的是两个状态变量i、matchCount，i 用于记录解析到了短信协议中的哪一行（/n），matchCount 记录在当前行中读取到了哪一个字节。状态变量在解码器中经常被使用，我们这里的情况比较简单，因为我们假定短信发送是在一次数据发送中完成的，所以状态变量的使用也比较简单。假如数据的发送被拆成了多次（譬如：短信协议的短信内容、消息报头被拆成了两次数据发送），那么上面的代码势必就会存在问题，因为当第二次调用doDecode()方法时，状态变量i、matchCount 势必会被重置，也就是原来的状态值并没有被保存。那么我们如何解决状态保存的问题呢？答案就是将状态变量保存在IoSession 中或者是Decoder 实例自身，但推荐使用前者，因为虽然Decoder 是单例的，其中的实例变量保存的状态在Decoder 实例销毁前始终保持，但Mina 并不保证每次调用doDecode()方法时都是同一个线程（这也就是说第一次调用doDecode()是IoProcessor-1 线程，第二次有可能就是IoProcessor-2 线程），这就会产生多线程中的实例变量的可视性（Visibility，具体请参考JAVA 的多线程知识）问题。IoSession中使用一个同步的HashMap 保存对象，所以你不需要担心多线程带来的问题。使用IoSession 保存解码器的状态变量通常的写法如下所示：</li>
<li>在解码器中定义私有的内部类Context，然后将需要保存的状态变量定义在Context 中存储。</li>
<li>在解码器中定义方法获取这个Context 的实例，这个方法的实现要优先从IoSession 中获取Context。<br>具体代码示例如下所示：<br>// 上下文作为保存状态的内部类的名字，意思很明显，就是让状态跟随上下文，在整个调用过程中都可以被保持。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.AttributeKey;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.CumulativeProtocolDecoder;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolDecoderOutput;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">XXXDecoder</span> <span class="keyword">extends</span> <span class="title">CumulativeProtocolDecoder</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AttributeKey CONTEXT = <span class="keyword">new</span> AttributeKey(getClass(), <span class="string">"context"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Context <span class="title">getContext</span><span class="params">(IoSession session)</span> </span>&#123;</span><br><span class="line">        Context ctx = (Context) session.getAttribute(CONTEXT);</span><br><span class="line">        <span class="keyword">if</span> (ctx == <span class="keyword">null</span>) &#123;</span><br><span class="line">            ctx = <span class="keyword">new</span> Context();</span><br><span class="line">            session.setAttribute(CONTEXT, ctx);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ctx;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">Context</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 状态变量</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">doDecode</span><span class="params">(IoSession session, IoBuffer in, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
注意这里我们使用了Mina 自带的AttributeKey 类来定义保存在IoSession 中的对象的键值，这样可以有效的防止键值重复。另外，要注意在全部处理完毕之后，状态要复位，譬如：聊天室中的一条消息读取完毕之后，状态变量要变为初始值，以便下次处理时重新使用。</li>
</ol>
<ul>
<li>第四步，编解码工厂：<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolDecoder;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolEncoder;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CmccSipcCodecFactory</span> <span class="keyword">implements</span> <span class="title">ProtocolCodecFactory</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CmccSipcEncoder encoder;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CmccSipcDecoder decoder;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CmccSipcCodecFactory</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(Charset.defaultCharset());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CmccSipcCodecFactory</span><span class="params">(Charset charSet)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.encoder = <span class="keyword">new</span> CmccSipcEncoder(charSet);</span><br><span class="line">        <span class="keyword">this</span>.decoder = <span class="keyword">new</span> CmccSipcDecoder(charSet);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ProtocolDecoder <span class="title">getDecoder</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> decoder;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ProtocolEncoder <span class="title">getEncoder</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> encoder;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
实际上这个工厂类就是包装了编码器、解码器，通过接口中的getEncoder()、getDecoder()方法向ProtocolCodecFilter 过滤器返回编解码器实例，以便在过滤器中对数据进行编解码处理。<br>第五步，运行示例：<br>下面我们修改最一开始的示例中的MyServer、MyClient 的代码，如下所示：<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoAcceptor;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IdleStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.LineDelimiter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.TextLineCodecFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.util.ReferenceCountingFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketAcceptor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.dxz.minademo2.TCPServerHandler;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPServer3</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line"></span><br><span class="line">        IoAcceptor acceptor = <span class="keyword">new</span> NioSocketAcceptor();</span><br><span class="line">        acceptor.getSessionConfig().setReadBufferSize(<span class="number">2048</span>);</span><br><span class="line">        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, <span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 编写过滤器</span></span><br><span class="line"></span><br><span class="line">        acceptor.getFilterChain().addLast(<span class="string">"codec"</span>,</span><br><span class="line">                <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> CmccSipcCodecFactory(Charset.forName(<span class="string">"UTF-8"</span>))));</span><br><span class="line">        </span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置handler</span></span><br><span class="line">        acceptor.getFilterChain().addLast(<span class="string">"myIoFilter"</span>, <span class="keyword">new</span> ReferenceCountingFilter(<span class="keyword">new</span> MyIoFilter()));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置handler</span></span><br><span class="line">        acceptor.setHandler(<span class="keyword">new</span> TCPServerHandler());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 绑定端口</span></span><br><span class="line">        acceptor.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">9124</span>));</span><br><span class="line"></span><br><span class="line">        System.out.println(acceptor.getSessionConfig());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
client<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.dxz.minademo3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.future.ConnectFuture;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.future.IoFutureListener;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoConnector;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.LineDelimiter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.textline.TextLineCodecFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketConnector;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.dxz.minademo2.TCPClientHandler;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TCPClient3</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        IoConnector connector = <span class="keyword">new</span> NioSocketConnector();</span><br><span class="line">        connector.setConnectTimeoutMillis(<span class="number">30000</span>);</span><br><span class="line"></span><br><span class="line">        connector.getFilterChain().addLast(<span class="string">"codec"</span>,</span><br><span class="line">                <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> CmccSipcCodecFactory(Charset.forName(<span class="string">"UTF-8"</span>))));</span><br><span class="line"></span><br><span class="line">        connector.setHandler(<span class="keyword">new</span> TCPClientHandler(<span class="string">"你好！\r\n 大家好！"</span>));</span><br><span class="line">        ConnectFuture future = connector.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"localhost"</span>, <span class="number">9124</span>));</span><br><span class="line">        future.addListener(<span class="keyword">new</span> IoFutureListener&lt;ConnectFuture&gt;() &#123;</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">operationComplete</span><span class="params">(ConnectFuture future)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                IoSession session = future.getSession();</span><br><span class="line">                System.out.println(<span class="string">"++++++++++++++++++++++++++++"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        System.out.println(<span class="string">"*************"</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
最后我们在ClientHandler中发送这条短信息：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">    public void sessionOpened(IoSession ioSession) throws Exception &#123;</span><br><span class="line">        SmsObject sm &#x3D; new SmsObject();</span><br><span class="line">        sm.setMessage(&quot;hemaceshi&quot;);</span><br><span class="line">        sm.setReceiver(&quot;1799999999&quot;);</span><br><span class="line">        sm.setSender(&quot;17888888888&quot;);</span><br><span class="line">        ioSession.write(sm);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
在ServerHanlder中接收这条信息<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">    public void messageReceived(IoSession ioSession, Object message) throws Exception &#123;</span><br><span class="line">        SmsObject sm &#x3D; (SmsObject) message;</span><br><span class="line">        ioSession.write(sm);</span><br><span class="line">        System.out.println(sm.getMessage());</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
你会看到Server 端的控制台输出如下信息：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">-%%%%%%%%%%%%%%%%%%%%%%%%%%%messageReceived</span><br><span class="line">The message received is [hemaceshi]</span><br><span class="line">-----------------messageReceived</span><br><span class="line">%%%%%%%%%%%%%%%%%%%%%%%%%%%filterWrite</span><br><span class="line">hemaceshi</span><br></pre></td></tr></table></figure>

</li>
</ul>
<h3 id="复杂解码器"><a href="#复杂解码器" class="headerlink" title="复杂解码器"></a>复杂解码器</h3><p>下面我们讲解一下如何在解码器中保存状态变量，也就是真正的实现上面所说的Context。<br>我们假设这样一种情况，有两条短信：<br>M sip:wap.fetion.com.cn SIP-C/2.0<br>S: 1580101xxxx<br>R: 1889020xxxx<br>L: 21<br>Hello World!<br>M sip:wap.fetion.com.cn SIP-C/2.0<br>S: 1580101xxxx<br>R: 1889020xxxx<br>L: 21<br>Hello World!<br>他们按照上面的颜色标识发送，也就是说红色部分、蓝色部分、绿色部分分别发送（调用三次IoSession.write()方法），那么如果你还用上面的CmccSipcDecoder，将无法工作，因为第一次数据流（红色部分）发送过取时，数据是不完整的，无法解析出一条短信息，当二次数据流（蓝色部分）发送过去时，已经可以解析出第一条短信息了，但是第二条短信还是不完整的，需要等待第三次数据流（绿色部分）的发送。注意：由于模拟数据发送的规模性问题很麻烦，所以这里采用了这种极端的例子说明问题，虽不具有典型性，但很能说明问题，这就足够了，所以不要追究这种发送消息是否在真实环境中存在，更不要追究其合理性。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> codec;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.AttributeKey;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.CumulativeProtocolDecoder;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolDecoderOutput;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.charset.Charset;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.CharsetDecoder;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CmccSipcDecoder</span> <span class="keyword">extends</span> <span class="title">CumulativeProtocolDecoder</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Charset charset;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AttributeKey CONTEXT = <span class="keyword">new</span> AttributeKey(getClass(),</span><br><span class="line">            <span class="string">"context"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CmccSipcDecoder</span><span class="params">(Charset charset)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.charset = charset;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">doDecode</span><span class="params">(IoSession session, IoBuffer in,</span></span></span><br><span class="line"><span class="function"><span class="params">                               ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Context ctx = getContext(session);</span><br><span class="line">        CharsetDecoder cd = charset.newDecoder();</span><br><span class="line">        <span class="keyword">int</span> matchCount = ctx.getMatchCount();</span><br><span class="line">        <span class="keyword">int</span> line = ctx.getLine();</span><br><span class="line">        IoBuffer buffer = ctx.innerBuffer;</span><br><span class="line">        String statusLine = ctx.getStatusLine(),</span><br><span class="line">                sender = ctx.getSender(),</span><br><span class="line">                receiver = ctx.getReceiver(),</span><br><span class="line">                length = ctx.getLength(),</span><br><span class="line">                sms = ctx.getSms();</span><br><span class="line">        <span class="keyword">while</span> (in.hasRemaining()) &#123;</span><br><span class="line">            <span class="keyword">byte</span> b = in.get();</span><br><span class="line">            matchCount++;</span><br><span class="line">            buffer.put(b);</span><br><span class="line">            <span class="keyword">if</span> (line &lt; <span class="number">4</span> &amp;&amp; b == <span class="number">10</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (line == <span class="number">0</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    statusLine = buffer.getString(matchCount, cd);</span><br><span class="line">                    statusLine = statusLine.substring(<span class="number">0</span>,</span><br><span class="line">                            statusLine.length() - <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                    ctx.setStatusLine(statusLine);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (line == <span class="number">1</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    sender = buffer.getString(matchCount, cd);</span><br><span class="line">                    sender = sender.substring(<span class="number">0</span>, sender.length() - <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                    ctx.setSender(sender);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (line == <span class="number">2</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    receiver = buffer.getString(matchCount, cd);</span><br><span class="line">                    receiver = receiver.substring(<span class="number">0</span>, receiver.length() -</span><br><span class="line">                            <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                    ctx.setReceiver(receiver);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (line == <span class="number">3</span>) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    length = buffer.getString(matchCount, cd);</span><br><span class="line">                    length = length.substring(<span class="number">0</span>, length.length() - <span class="number">1</span>);</span><br><span class="line">                    matchCount = <span class="number">0</span>;</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                    ctx.setLength(length);</span><br><span class="line">                &#125;</span><br><span class="line">                line++;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (line == <span class="number">4</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (matchCount == Long.parseLong(length.split(<span class="string">": "</span>)[<span class="number">1</span>])) &#123;</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    sms = buffer.getString(matchCount, cd);</span><br><span class="line">                    ctx.setSms(sms);</span><br><span class="line">                    <span class="comment">// 由于下面的break，这里需要调用else外面的两行代码</span></span><br><span class="line">                    ctx.setMatchCount(matchCount);</span><br><span class="line">                    ctx.setLine(line);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            ctx.setMatchCount(matchCount);</span><br><span class="line">            ctx.setLine(line);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (ctx.getLine() == <span class="number">4</span></span><br><span class="line">                &amp;&amp; Long.parseLong(ctx.getLength().split(<span class="string">": "</span>)[<span class="number">1</span>]) == ctx</span><br><span class="line">                .getMatchCount()) &#123;</span><br><span class="line">            SmsObject smsObject = <span class="keyword">new</span> SmsObject();</span><br><span class="line">            smsObject.setSender(sender.split(<span class="string">": "</span>)[<span class="number">1</span>]);</span><br><span class="line">            smsObject.setReceiver(receiver.split(<span class="string">": "</span>)[<span class="number">1</span>]);</span><br><span class="line">            smsObject.setMessage(sms);</span><br><span class="line">            out.write(smsObject);</span><br><span class="line">            ctx.reset();</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> Context <span class="title">getContext</span><span class="params">(IoSession session)</span> </span>&#123;</span><br><span class="line">        Context context = (Context) session.getAttribute(CONTEXT);</span><br><span class="line">        <span class="keyword">if</span> (context == <span class="keyword">null</span>) &#123;</span><br><span class="line">            context = <span class="keyword">new</span> Context();</span><br><span class="line">            session.setAttribute(CONTEXT, context);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> context;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">Context</span> </span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> IoBuffer innerBuffer;</span><br><span class="line">        <span class="keyword">private</span> String statusLine = <span class="string">""</span>;</span><br><span class="line">        <span class="keyword">private</span> String sender = <span class="string">""</span>;</span><br><span class="line">        <span class="keyword">private</span> String receiver = <span class="string">""</span>;</span><br><span class="line">        <span class="keyword">private</span> String length = <span class="string">""</span>;</span><br><span class="line">        <span class="keyword">private</span> String sms = <span class="string">""</span>;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">Context</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            innerBuffer = IoBuffer.allocate(<span class="number">100</span>).setAutoExpand(<span class="keyword">true</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> matchCount = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> line = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getMatchCount</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> matchCount;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setMatchCount</span><span class="params">(<span class="keyword">int</span> matchCount)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.matchCount = matchCount;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getLine</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> line;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setLine</span><span class="params">(<span class="keyword">int</span> line)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.line = line;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> String <span class="title">getStatusLine</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> statusLine;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setStatusLine</span><span class="params">(String statusLine)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.statusLine = statusLine;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> String <span class="title">getSender</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> sender;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setSender</span><span class="params">(String sender)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.sender = sender;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> String <span class="title">getReceiver</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> receiver;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setReceiver</span><span class="params">(String receiver)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.receiver = receiver;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> String <span class="title">getLength</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> length;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setLength</span><span class="params">(String length)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.length = length;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> String <span class="title">getSms</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> sms;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setSms</span><span class="params">(String sms)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.sms = sms;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reset</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.innerBuffer.clear();</span><br><span class="line">            <span class="keyword">this</span>.matchCount = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">this</span>.line = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">this</span>.statusLine = <span class="string">""</span>;</span><br><span class="line">            <span class="keyword">this</span>.sender = <span class="string">""</span>;</span><br><span class="line">            <span class="keyword">this</span>.receiver = <span class="string">""</span>;</span><br><span class="line">            <span class="keyword">this</span>.length = <span class="string">""</span>;</span><br><span class="line">            <span class="keyword">this</span>.sms = <span class="string">""</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里我们做了如下的几步操作：</p>
<ol>
<li>所有记录状态的变量移到了Context 内部类中，包括记录读到短信协议的哪一行的line。每一行读取了多少个字节的matchCount，还有记录解析好的状态行、发送者、接受者、短信内容、累积数据的innerBuffer 等。这样就可以在数据不能完全解码，等待下一次doDecode()方法的调用时，还能承接上一次调用的数据。</li>
<li>在doDecode()方法中主要的变化是各种状态变量首先是从Context 中获取，然后操作之后，将最新的值setXXX()到Context 中保存。</li>
<li>这里注意doDecode()方法最后的判断，当认为不够解码为一条短信息时，返回false，也就是在本次数据流解码中不要再调用doDecode()方法；当认为已经解码出一条短信息时，输出短消息，然后重置所有的状态变量，返回true，也就是如果本次数据流解码中还有没解码完的数据，继续调用doDecode()方法。下面我们对客户端稍加改造，来模拟上面的红、蓝、绿三次发送聊天短信息的情况：<br>MyClient：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">ConnectFuture future &#x3D; connector.connect(new InetSocketAddress(&quot;localhost&quot;, 8888));</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 等待是否连接成功，相当于是转异步执行为同步执行</span><br><span class="line">future.awaitUninterruptibly();</span><br><span class="line">&#x2F;&#x2F; 连接成功后获取会话对象。如果没有上面的等待，由于connect()方法是异步的，session可能无法被获取</span><br><span class="line">IoSession session &#x3D; future.getSession();</span><br><span class="line">for (int i &#x3D; 0; i &lt; 3; i++) &#123;</span><br><span class="line">    SmsObject smsObject &#x3D; new SmsObject();</span><br><span class="line">    session.write(smsObject);</span><br><span class="line">    System.out.println(&quot;*********&quot; + i);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
这里我们为了方便演示，不在IoHandler 中发送消息，而是直接在MyClient 中发送，你要注意的是三次发送都要使用同一个IoSession，否则就不是从同一个通道发送过去的了。<br>CmccSipcEncoder：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public void encode(IoSession session, Object message,</span><br><span class="line">                   ProtocolEncoderOutput out) throws Exception &#123;</span><br><span class="line">    SmsObject sms &#x3D; (SmsObject) message;</span><br><span class="line">    CharsetEncoder ce &#x3D; charset.newEncoder();</span><br><span class="line">    String statusLine &#x3D; &quot;M sip:wap.fetion.com.cn SIP-C&#x2F;2.0&quot;;</span><br><span class="line">    String sender &#x3D; &quot;15801012253&quot;;</span><br><span class="line">    String receiver &#x3D; &quot;15866332698&quot;;</span><br><span class="line">    String smsContent &#x3D; &quot;你好！Hello World!&quot;;</span><br><span class="line">    IoBuffer buffer &#x3D; IoBuffer.allocate(100).setAutoExpand(true);</span><br><span class="line">    buffer.putString(statusLine + &quot;&#x2F;n&quot;, ce);</span><br><span class="line">    buffer.putString(&quot;S: &quot; + sender + &quot;&#x2F;n&quot;, ce);</span><br><span class="line">    buffer.putString(&quot;R: &quot; + receiver + &quot;&#x2F;n&quot;, ce);</span><br><span class="line">    buffer.flip();</span><br><span class="line">    out.write(buffer);</span><br><span class="line">    IoBuffer buffer2 &#x3D; IoBuffer.allocate(100).setAutoExpand(true);</span><br><span class="line">    buffer2.putString(&quot;L: &quot; + (smsContent.getBytes(charset).length)</span><br><span class="line">            + &quot;&#x2F;n&quot;,ce);</span><br><span class="line">    buffer2.putString(smsContent, ce);</span><br><span class="line">    buffer2.putString(statusLine + &quot;&#x2F;n&quot;, ce);</span><br><span class="line">    buffer2.flip();</span><br><span class="line">    out.write(buffer2);</span><br><span class="line">    IoBuffer buffer3 &#x3D; IoBuffer.allocate(100).setAutoExpand(true);</span><br><span class="line">    buffer3.putString(&quot;S: &quot; + sender + &quot;&#x2F;n&quot;, ce);</span><br><span class="line">    buffer3.putString(&quot;R: &quot; + receiver + &quot;&#x2F;n&quot;, ce);</span><br><span class="line">    buffer3.putString(&quot;L: &quot; + (smsContent.getBytes(charset).length)</span><br><span class="line">            + &quot;&#x2F;n&quot;,ce);</span><br><span class="line">    buffer3.putString(smsContent, ce);</span><br><span class="line">    buffer3.putString(statusLine + &quot;&#x2F;n&quot;, ce);</span><br><span class="line">    buffer3.flip();</span><br><span class="line">    out.write(buffer3);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
上面的这段代码要配合MyClient来操作，你需要做的是在MyClient中的红色输出语句处设置断点，然后第一调用时CmccSipcEncoder中注释掉蓝、绿色的代码，也就是发送两条短信息的第一部分（红色的代码），依次类推，也就是MyClient的中的三次断点中，分别执行CmccSipcEncoder中的红、蓝、绿三段代码，也就是模拟两条短信的三段发送。你会看到Server端的运行结果是：当MyClient第一次到达断点时，没有短信息被读取到，当MyClient第二次到达断点时，第一条短信息输出，当MyClient第三次到达断点时，第二条短信息输出。</li>
</ol>
<p>Mina中自带的解码器：</p>
<p>解码器 说明<br><code>CumulativeProtocolDecoder 累积性解码器</code>，上面我们重点说明了这个解码器的用法。<br>SynchronizedProtocolDecoder 这个解码器用于将任何一个解码器包装为一个线程安全的解码器，用于解决上面说的每次执行decode()方法时可能线程不是上一次的线程的问题，但这样会在高并发时，大大降低系统的性能。<br>TextLineDecoder 按照文本的换行符（ Windows:/r/n 、Linux:/n、Mac:/r）解码数据。<br>PrefixedStringDecoder 这个类继承自CumulativeProtocolDecoder类，用于读取数据最前端的1、2、4 个字节表示后面的数据长度的数据。譬如：一个段数据的前两个字节表示后面的真实数据的长度，那么你就可以用这个方法进行解码。</p>
<h3 id="多路分离的解码器"><a href="#多路分离的解码器" class="headerlink" title="多路分离的解码器"></a>多路分离的解码器</h3><p>假设一段数据发送过来之后，需要根据某种条件决定使用哪个解码器，而不是像上面的例子，固定使用一个解码器，那么该如何做呢？幸好Mina 提供了org.apache.mina.filter.codec.demux 包来完成这种多路分离（Demultiplexes）的解码工作，也就是同时注册多个解码器，然后运行时依据传入的数据决定到底使用哪个解码器来工作。<code>所谓多路分离就是依据条件分发到指定的解码器</code>，譬如：上面的短信协议进行扩展，可以依据状态行来判断使用1.0 版本的短信协议解码器还是2.0版本的短信协议解码器。<br>下面我们使用一个简单的例子，说明这个多路分离的解码器是如何使用的，需求如下所示：</p>
<ol>
<li><p>客户端传入两个int 类型的数字，还有一个char 类型的符号。</p>
</li>
<li><p>如果符号是+，服务端就是用1 号解码器，对两个数字相加，然后把结果返回给客户端。</p>
</li>
<li><p>如果符号是-，服务端就使用2 号解码器，将两个数字变为相反数，然后相加，把结果返回给客户端。<br>Demux 开发编解码器主要有如下几个步骤：</p>
<p> A. 定义Client 端、Server 端发送、接收的数据对象。</p>
<p> B. 使用Demux 编写编码器是实现MessageEncoder<T>接口，T 是你要编码的数据对象，这个MessageEncoder 会在DemuxingProtocolEncoder 中调用。</p>
<p> C. 使用Demux 编写编码器是实现MessageDecoder 接口，这个MessageDecoder 会在DemuxingProtocolDecoder 中调用。</p>
<p> D. 在 DemuxingProtocolCodecFactory 中调用addMessageEncoder()、addMessageDecoder()方法组装编解码器。</p>
</li>
</ol>
<p>MessageEncoder的接口如下所示</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MessageEncoder</span>&lt;<span class="title">T</span>&gt; </span>&#123;    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">encode</span><span class="params">(IoSession session, T message, ProtocolEncoderOutput out)</span> <span class="keyword">throws</span> Exception</span>;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>你注意到消息编码器接口与在ProtocolEncoder 中没什么不同，区别就是Object message被泛型具体化了类型，你不需要手动的类型转换了。<br>MessageDecoder的接口如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MessageDecoder</span> </span>&#123;    </span><br><span class="line">    <span class="keyword">static</span> MessageDecoderResult OK = MessageDecoderResult.OK;    </span><br><span class="line">    <span class="keyword">static</span> MessageDecoderResult NEED_DATA = MessageDecoderResult.NEED_DATA;    </span><br><span class="line">    <span class="keyword">static</span> MessageDecoderResult NOT_OK = MessageDecoderResult.NOT_OK;    </span><br><span class="line">    <span class="function">MessageDecoderResult <span class="title">decodable</span><span class="params">(IoSession session, IoBuffer in)</span></span>;    </span><br><span class="line">    <span class="function">MessageDecoderResult <span class="title">decode</span><span class="params">(IoSession session, IoBuffer in,    </span></span></span><br><span class="line"><span class="function"><span class="params">    ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception</span>;    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">finishDecode</span><span class="params">(IoSession session, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception</span>;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ol>
<li><p>decodable()方法有三个返回值，分别表示如下的含义：</p>
<p> A. MessageDecoderResult.NOT_OK：表示这个解码器不适合解码数据，然后检查其它解码器，如果都不满足会抛异常；</p>
<p> B. MessageDecoderResult.NEED_DATA：表示当前的读入的数据不够判断是否能够使用这个解码器解码，然后再次调用decodable()方法检查其它解码器，如果都是NEED_DATA,则等待下次输入；</p>
<p> C. MessageDecoderResult.OK： 表示这个解码器可以解码读入的数据， 然后则调用MessageDecoder 的decode()方法。<code>这里注意decodable()方法对参数IoBuffer in 的任何操作在方法结束之后，都会复原</code>，也就是你不必担心在调用decode()方法时，position 已经不在缓冲区的起始位置。这个方法相当于是预读取，用于判断是否是可用的解码器。</p>
</li>
<li><p>decode()方法有三个返回值，分别表示如下的含义：</p>
<p> A. MessageDecoderResult.NOT_OK：表示解码失败，会抛异常；<br> B. MessageDecoderResult.NEED_DATA：表示数据不够，需要读到新的数据后，再次调用decode()方法。<br> C. MessageDecoderResult.OK：表示解码成功。</p>
</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"><span class="comment">//客户端发送的数据对象</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SendMessage</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> j = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">char</span> symbol = <span class="string">'+'</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">char</span> <span class="title">getSymbol</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> symbol;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setSymbol</span><span class="params">(<span class="keyword">char</span> symbol)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.symbol = symbol;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getI</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> i;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setI</span><span class="params">(<span class="keyword">int</span> i)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.i = i;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getJ</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> j;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setJ</span><span class="params">(<span class="keyword">int</span> j)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.j = j;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"><span class="comment">//服务端发送的返回结果对象</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResultMessage</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getResult</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setResult</span><span class="params">(<span class="keyword">int</span> result)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.result = result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolEncoderOutput;</span><br><span class="line"><span class="comment">//客户端使用的SendMessage的编码器</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SendSmsMessageEncoder</span> <span class="keyword">implements</span> <span class="title">SmsMessageEncoder</span>&lt;<span class="title">SendMessage</span>&gt; </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">encode</span><span class="params">(IoSession session, SendMessage message, ProtocolEncoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        IoBuffer buffer = IoBuffer.allocate(<span class="number">10</span>);</span><br><span class="line">        buffer.putChar(message.getSymbol());</span><br><span class="line">        buffer.putInt(message.getI());</span><br><span class="line">        buffer.putInt(message.getJ());</span><br><span class="line">        buffer.flip();</span><br><span class="line">        out.write(buffer);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里我们的SendMessage、ResultMessage 中的字段都是用长度固定的基本数据类型，这样IoBuffer 就不需要自动扩展了，提高性能。按照一个char、两个int 计算，这里的IoBuffer只需要10 个字节的长度就可以了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolDecoderOutput;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.demux.MessageDecoder;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.demux.MessageDecoderResult;</span><br><span class="line"><span class="comment">//服务端使用的SendMessage的1号解码器</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SendMessageDecoderPositive</span> <span class="keyword">implements</span> <span class="title">MessageDecoder</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MessageDecoderResult <span class="title">decodable</span><span class="params">(IoSession session, IoBuffer in)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (in.remaining() &lt; <span class="number">2</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> MessageDecoderResult.NEED_DATA;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">char</span> symbol = in.getChar();</span><br><span class="line">            <span class="keyword">if</span> (symbol == <span class="string">'+'</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> MessageDecoderResult.OK;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> MessageDecoderResult.NOT_OK;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MessageDecoderResult <span class="title">decode</span><span class="params">(IoSession session, IoBuffer in, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        SendMessage sm = <span class="keyword">new</span> SendMessage();</span><br><span class="line">        sm.setSymbol(in.getChar());</span><br><span class="line">        sm.setI(in.getInt());</span><br><span class="line">        sm.setJ(in.getInt());</span><br><span class="line">        out.write(sm);</span><br><span class="line">        <span class="keyword">return</span> MessageDecoderResult.OK;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">finishDecode</span><span class="params">(IoSession session, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// undo</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>因为客户端发送的SendMessage 的前两个字节（char）就是符号位，所以我们在decodable()方法中对此条件进行了判断，之后读到两个字节，并且这两个字节表示的字符是+时，才认为这个解码器可用。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolDecoderOutput;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.demux.MessageDecoderResult;</span><br><span class="line"><span class="comment">//服务端使用的SendMessage的2号解码器</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SendSmsMessageDecoderNegative</span> <span class="keyword">implements</span> <span class="title">SmsMessageDecoder</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MessageDecoderResult <span class="title">decodable</span><span class="params">(IoSession session, IoBuffer in)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (in.remaining() &lt; <span class="number">2</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> MessageDecoderResult.NEED_DATA;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">char</span> symbol = in.getChar();</span><br><span class="line">            <span class="keyword">if</span> (symbol == <span class="string">'-'</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> MessageDecoderResult.OK;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> MessageDecoderResult.NOT_OK;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MessageDecoderResult <span class="title">decode</span><span class="params">(IoSession session, IoBuffer in, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        SendMessage sm = <span class="keyword">new</span> SendMessage();</span><br><span class="line">        sm.setSymbol(in.getChar());</span><br><span class="line">        sm.setI(-in.getInt());</span><br><span class="line">        sm.setJ(-in.getInt());</span><br><span class="line">        out.write(sm);</span><br><span class="line">        <span class="keyword">return</span> MessageDecoderResult.OK;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">finishDecode</span><span class="params">(IoSession session, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// undo</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"><span class="comment">//服务端使用的ResultMessage的编码器</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResultMessage</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getResult</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setResult</span><span class="params">(<span class="keyword">int</span> result)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.result = result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.buffer.IoBuffer;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolDecoderOutput;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.demux.MessageDecoder;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.demux.MessageDecoderResult;</span><br><span class="line"><span class="comment">//客户端使用的ResultMessage的解码器</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResultMessageDecoder</span> <span class="keyword">implements</span> <span class="title">MessageDecoder</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MessageDecoderResult <span class="title">decodable</span><span class="params">(IoSession session, IoBuffer in)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (in.remaining() &lt; <span class="number">4</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> MessageDecoderResult.NEED_DATA;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (in.remaining() == <span class="number">4</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> MessageDecoderResult.OK;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> MessageDecoderResult.NOT_OK;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MessageDecoderResult <span class="title">decode</span><span class="params">(IoSession session, IoBuffer in, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ResultMessage rm = <span class="keyword">new</span> ResultMessage();</span><br><span class="line">        rm.setResult(in.getInt());</span><br><span class="line">        out.write(rm);</span><br><span class="line">        <span class="keyword">return</span> MessageDecoderResult.OK;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">finishDecode</span><span class="params">(IoSession session, ProtocolDecoderOutput out)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// undo</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory;</span><br><span class="line"><span class="comment">//组装这些编解码器的工厂</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MathProtocolCodecFactory</span> <span class="keyword">extends</span> <span class="title">DemuxingProtocolCodecFactory</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MathProtocolCodecFactory</span><span class="params">(<span class="keyword">boolean</span> server)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (server) &#123;</span><br><span class="line">            <span class="keyword">super</span>.addMessageEncoder(ResultMessage<span class="class">.<span class="keyword">class</span>, <span class="title">ResultMessageEncoder</span>.<span class="title">class</span>)</span>;</span><br><span class="line">            <span class="keyword">super</span>.addMessageDecoder(SendMessageDecoderPositive<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">            <span class="keyword">super</span>.addMessageDecoder(SendSmsMessageDecoderNegative<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">super</span>.addMessageEncoder(SendMessage<span class="class">.<span class="keyword">class</span>, <span class="title">SendSmsMessageEncoder</span>.<span class="title">class</span>)</span>;</span><br><span class="line">            <span class="keyword">super</span>.addMessageDecoder(ResultMessageDecoder<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这个工厂类我们使用了构造方法的一个布尔类型的参数，以便其可以在Server 端、Client端同时使用。我们以Server 端为例，你可以看到调用两次addMessageDecoder()方法添加了1 号、2 号解码器，其实DemuxingProtocolDecoder 内部在维护了一个MessageDecoder数组，用于保存添加的所有的消息解码器，每次decode()的时候就调用每个MessageDecoder的decodable()方法逐个检查，只要发现一个MessageDecoder 不是对应的解码器，就从数组中移除，直到找到合适的MessageDecoder，如果最后发现数组为空，就表示没找到对应的MessageDecoder，最后抛出异常。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoAcceptor;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IdleStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.logging.LoggingFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketAcceptor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"><span class="comment">//Server端</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Server</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        IoAcceptor acceptor = <span class="keyword">new</span> NioSocketAcceptor();</span><br><span class="line">        LoggingFilter lf = <span class="keyword">new</span> LoggingFilter();</span><br><span class="line">        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, <span class="number">5</span>);</span><br><span class="line">        acceptor.getFilterChain().addLast(<span class="string">"logger"</span>, lf);</span><br><span class="line">        acceptor.getFilterChain().addLast(<span class="string">"codec"</span>, <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> MathProtocolCodecFactory(<span class="keyword">true</span>)));</span><br><span class="line">        acceptor.setHandler(<span class="keyword">new</span> ServerHandler());</span><br><span class="line">        acceptor.bind(<span class="keyword">new</span> InetSocketAddress(<span class="number">9123</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoHandlerAdapter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IdleStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"><span class="comment">//ServerHandler</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ServerHandler</span> <span class="keyword">extends</span> <span class="title">IoHandlerAdapter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> Logger log = LoggerFactory.getLogger(ServerHandler<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionIdle</span><span class="params">(IoSession session, IdleStatus status)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        session.close(<span class="keyword">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">messageReceived</span><span class="params">(IoSession session, Object message)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        SendMessage sm = (SendMessage) message;</span><br><span class="line">        System.out.println(<span class="string">"The message received is [ "</span> + sm.getI() + <span class="string">" "</span> + sm.getSymbol() + <span class="string">" "</span> + sm.getJ() + <span class="string">" ]"</span>);</span><br><span class="line">        ResultMessage rm = <span class="keyword">new</span> ResultMessage();</span><br><span class="line">        rm.setResult(sm.getI() + sm.getJ());</span><br><span class="line">        session.write(rm);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net.InetSocketAddress;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoConnector;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.codec.ProtocolCodecFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.filter.logging.LoggingFilter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.transport.socket.nio.NioSocketConnector;</span><br><span class="line"><span class="comment">//Client端</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Client</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">        IoConnector connector = <span class="keyword">new</span> NioSocketConnector();</span><br><span class="line">        connector.setConnectTimeoutMillis(<span class="number">30000</span>);</span><br><span class="line">        connector.getFilterChain().addLast(<span class="string">"logger"</span>, <span class="keyword">new</span> LoggingFilter());</span><br><span class="line">        connector.getFilterChain().addLast(<span class="string">"codec"</span>, <span class="keyword">new</span> ProtocolCodecFilter(<span class="keyword">new</span> MathProtocolCodecFactory(<span class="keyword">false</span>)));</span><br><span class="line">        connector.setHandler(<span class="keyword">new</span> ClientHandler());</span><br><span class="line">        connector.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"localhost"</span>, <span class="number">9123</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> multichannel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.service.IoHandlerAdapter;</span><br><span class="line"><span class="keyword">import</span> org.apache.mina.core.session.IoSession;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory;</span><br><span class="line"><span class="comment">//client的Hanlder</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ClientHandler</span> <span class="keyword">extends</span> <span class="title">IoHandlerAdapter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> Logger LOGGER = LoggerFactory.getLogger(ClientHandler<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sessionOpened</span><span class="params">(IoSession session)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        SendMessage sm = <span class="keyword">new</span> SendMessage();</span><br><span class="line">        sm.setI(<span class="number">100</span>);</span><br><span class="line">        sm.setJ(<span class="number">99</span>);</span><br><span class="line">        sm.setSymbol(<span class="string">'+'</span>);</span><br><span class="line">        session.write(sm);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">messageReceived</span><span class="params">(IoSession session, Object message)</span> </span>&#123;</span><br><span class="line">        ResultMessage rs = (ResultMessage) message;</span><br><span class="line">        System.out.println(String.valueOf(rs.getResult()));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;结果</span><br><span class="line">The message received is [ -100 - -99 ]</span><br><span class="line">The message received is [ 100 + 99 ]</span><br></pre></td></tr></table></figure>
<h2 id="线程模型配置"><a href="#线程模型配置" class="headerlink" title="线程模型配置"></a>线程模型配置</h2><p>Mina 中的很多执行环节都使用了多线程机制，用于提高性能。<code>Mina 中默认在三个地方使用了线程</code>：</p>
<ol>
<li>IoAcceptor：<br>这个地方用于接受客户端的连接建立，每监听一个端口（每调用一次bind()方法），都启用一个线程，这个数字我们不能改变。这个线程监听某个端口是否有请求到来，一旦发现，则创建一个IoSession 对象。因为这个动作很快，所以有一个线程就够了。</li>
<li>IoConnector：<br>这个地方用于与服务端建立连接，每连接一个服务端（每调用一次connect()方法），就启用一个线程，我们不能改变。同样的，这个线程监听是否有连接被建立，一旦发现，则创建一个IoSession 对象。因为这个动作很快，所以有一个线程就够了。</li>
<li>IoProcessor：<br>这个地方用于执行真正的IO 操作，默认启用的线程个数是CPU 的核数+1，譬如：单CPU 双核的电脑，默认的IoProcessor 线程会创建3 个。这也就是说一个IoAcceptor 或者IoConnector 默认会关联一个IoProcessor 池，这个池中有3 个IoProcessor。因为IO 操作耗费资源，所以这里使用IoProcessor 池来完成数据的读写操作，有助于提高性能。这也就是前面说的IoAccetor、IoConnector 使用一个Selector，而IoProcessor 使用自己单独的Selector 的原因。那么为什么IoProcessor 池中的IoProcessor 数量只比CPU 的核数大1 呢？因为IO 读写操作是耗费CPU 的操作，而每一核CPU 同时只能运行一个线程，因此IoProcessor 池中的IoProcessor 的数量并不是越多越好。</li>
</ol>
<p>这个IoProcessor 的数量可以调整，如下所示：<br>IoAcceptor acceptor=new NioSocketAcceptor(5);<br>IoConnector connector=new NioSocketConnector(5);<br>这样就会将IoProcessor 池中的数量变为5 个，也就是说可以同时处理5 个读写操作。<code>还记得前面说过Mina 的解码器要使用IoSession 保存状态变量，而不是Decoder 本身，这是因为Mina 不保证每次执行doDecode()方法的都是同一个IoProcessor 这句话吗？</code>其实这个问题的根本原因是IoProcessor 是一个池，每次IoSession 进入空闲状态时（无读些数据发生），IoProcessor 都会被回收到池中，以便其他的IoSession 使用，所以当IoSession从空闲状态再次进入繁忙状态时，IoProcessor 会再次分配给其一个IoProcessor 实例，而此时已经不能保证还是上一次繁忙状态时的那个IoProcessor 了。<code>你还会发现IoAcceptor 、IoConnector 还有一个构造方法， 你可以指定一个java.util.concurrent.Executor 类作为线程池对象，那么这个线程池对象是做什么用的呢？</code>其实就是用于创建(1.)、(2.)中的用于监听是否有TCP 连接建立的那个线程，默认情况下，使用Executors.newCachedThreadPool()方法创建Executor 实例，也就是一个无界的线程池（具体内容请参看JAVA 的并发库）。大家不要试图改变这个Executor 的实例，也就是使用内置的即可，否则可能会造成一些莫名其妙的问题，譬如：性能在某个访问量级别时，突然下降。因为无界线程池是有多少个Socket 建立，就分配多少个线程，如果你改为Executors 的其他创建线程池的方法，创建了一个有界线程池，那么一些请求将无法得到及时响应，从而出现一些问题。</p>
<p><code>下面我们完整的综述一下Mina 的工作流程：</code></p>
<p><code>1. 当 IoService 实例创建的时候，同时一个关联在IoService上的IoProcessor 池、线程池也被创建；</code></p>
<p><code>2. 当 IoService 建立套接字（IoAcceptor 的bind()或者是IoConnector 的connect()方法被调用）时，IoService 从线程池中取出一个线程，监听套接字端口；</code></p>
<p><code>3. 当 IoService 监听到套接字上有连接请求时，建立IoSession 对象，从IoProcessor池中取出一个IoProcessor 实例执行这个会话通道上的过滤器、IoHandler；</code></p>
<p><code>4. 当这条IoSession 通道进入空闲状态或者关闭时，IoProcessor 被回收。上面说的是Mina 默认的线程工作方式，那么我们这里要讲的是如何配置IoProcessor 的多线程工作方式。因为一个IoProcessor 负责执行一个会话上的所有过滤器、IoHandler，也就是对于IO 读写操作来说，是单线程工作方式（就是按照顺序逐个执行）。假如你想让某个事件方法（譬如：sessionIdle()、sessionOpened()等）在单独的线程中运行（也就是非IoProcessor 所在的线程），那么这里就需要用到一个ExecutorFilter 的过滤器。你可以看到IoProcessor 的构造方法中有一个参数是java.util.concurrent.Executor，也就是可以让IoProcessor 调用的过滤器、IoHandler 中的某些事件方法在线程池中分配的线程上独立运行，而不是运行在IoProcessor 所在的线程。</code></p>
<p>例：<br>acceptor.getFilterChain().addLast(“exceutor”, new ExecutorFilter());<br>我们看到是用这个功能，简单的一行代码就可以了。那么ExecutorFilter 还有许多重载的构造方法，这些重载的有参构造方法，参数主要用于指定如下信息：</p>
<ol>
<li>指定线程池的属性信息，譬如：核心大小、最大大小、等待队列的性质等。你特别要关注的是ExecutorFilter 内部默认使用的是OrderedThreadPoolExecutor 作为线程池的实现，从名字上可以看出是保证各个事件在多线程执行中的顺序（譬如：各个事件方法的执行是排他的，也就是不可能出现两个事件方法被同时执行；messageReceived()总是在sessionClosed() 方法之前执行）， 这是因为多线程的执行是异步的， 如果没有OrderedThreadPoolExecutor 来保证IoHandler 中的方法的调用顺序，可能会出现严重的问题。但是如果你的代码确实没有依赖于IoHandler 中的事件方法的执行顺序，那么你可以使用UnorderedThreadPoolExecutor 作为线程池的实现。因此，你也最好不要改变默认的Executor 实现，否则，事件的执行顺序就会混乱，譬如：messageReceived()、messageSent()方法被同时执行。</li>
<li>哪些事件方法被关注，也就哪些事件方法用这个线程池执行。线程池可以异步执行的事件类型是位于IoEventType 中的九个枚举值中除了SESSION_CREATED 之外的其余八个，这说明Session 建立的事件只能与IoProcessor 在同一个线程上执行。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> IoEventType &#123;    </span><br><span class="line">SESSION_CREATED,    </span><br><span class="line">SESSION_OPENED,    </span><br><span class="line">SESSION_CLOSED,    </span><br><span class="line">MESSAGE_RECEIVED,    </span><br><span class="line">MESSAGE_SENT,    </span><br><span class="line">SESSION_IDLE,    </span><br><span class="line">EXCEPTION_CAUGHT,    </span><br><span class="line">WRITE,    </span><br><span class="line">CLOSE,    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
默认情况下，没有配置关注的事件类型，有如下六个事件方法会被自动使用线程池异步执行<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">IoEventType.EXCEPTION_CAUGHT,</span><br><span class="line">IoEventType.MESSAGE_RECEIVED,</span><br><span class="line">IoEventType.MESSAGE_SENT,</span><br><span class="line">IoEventType.SESSION_CLOSED,</span><br><span class="line">IoEventType.SESSION_IDLE,</span><br><span class="line">IoEventType.SESSION_OPENED</span><br></pre></td></tr></table></figure>
其实ExecutorFilter 的工作机制很简单，就是在调用下一个过滤器的事件方法时，把其交给Executor 的execute(Runnable runnable)方法来执行，其实你自己在IoHandler 或者某个过滤器的事件方法中开启一个线程，也可以完成同样的功能，只不过这样做，你就失去了程序的可配置性，线程调用的代码也会完全耦合在代码中。但要注意的是绝对不能开启线程让其执行sessionCreated()方法。如果你真的打算使用这个ExecutorFilter，那么最好想清楚它该放在过滤器链的哪个位置，针对哪些事件做异步处理机制。一般ExecutorFilter 都是要放在ProtocolCodecFilter 过滤器的后面，也就是不要让编解码运行在独立的线程上，而是要运行在IoProcessor 所在的线程，因为编解码处理的数据都是由IoProcessor 读取和发送的，没必要开启新的线程，否则性能反而会下降。一般使用ExecutorFilter 的典型场景是将业务逻辑（譬如：耗时的数据库操作）放在单独的线程中运行，也就是说与IO 处理无关的操作可以考虑使用ExecutorFilter 来异步执行。</li>
</ol>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/index.html" rel="tag"># 高并发</a>
          
            <a href="/tags/Mina/index.html" rel="tag"># Mina</a>
          
        </div>
      

      
      
        <div class="post-widgets">
        

        

        
          
          <div id="needsharebutton-postbottom">
            <span class="btn">
              <i class="fa fa-share-alt" aria-hidden="true"></i>
            </span>
          </div>
        
        </div>
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/p/55146056.html" rel="next" title="网络通信框架（1）---Mina">
                <i class="fa fa-chevron-left"></i> 网络通信框架（1）---Mina
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/p/8d5e8318.html" rel="prev" title="2020-06-21">
                2020-06-21 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
    </div>
  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/images/avatar.jpg"
                alt="MolZhao" />
            
              <p class="site-author-name" itemprop="name">MolZhao</p>
              <p class="site-description motion-element" itemprop="description">沉淀</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/index.html">
              
                  <span class="site-state-item-count">54</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">16</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">30</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a href="https://github.com/normcorer" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-github"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="mailto:MolZhaovo@163.com" target="_blank" title="Mail">
                      
                        <i class="fa fa-fw fa-envelope"></i>Mail</a>
                  </span>
                
            </div>
          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#简单的TCPServer"><span class="nav-number">1.</span> <span class="nav-text">简单的TCPServer</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#第一步：编写IoService"><span class="nav-number">1.1.</span> <span class="nav-text">第一步：编写IoService</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#第二步：编写过滤器"><span class="nav-number">1.2.</span> <span class="nav-text">第二步：编写过滤器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#第三步：编写IoHandler"><span class="nav-number">1.3.</span> <span class="nav-text">第三步：编写IoHandler</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#简单的TCPClient"><span class="nav-number">2.</span> <span class="nav-text">简单的TCPClient</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#第一步编写IoService并注册过滤器"><span class="nav-number">2.1.</span> <span class="nav-text">第一步编写IoService并注册过滤器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#第二步：编写IoHandler"><span class="nav-number">2.2.</span> <span class="nav-text">第二步：编写IoHandler</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#介绍Mina的TCP的主要接口"><span class="nav-number">3.</span> <span class="nav-text">介绍Mina的TCP的主要接口</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#日志配置"><span class="nav-number">4.</span> <span class="nav-text">日志配置</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#过滤器"><span class="nav-number">5.</span> <span class="nav-text">过滤器</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#协议编解码器"><span class="nav-number">6.</span> <span class="nav-text">协议编解码器</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#简单编解码器示例"><span class="nav-number">6.1.</span> <span class="nav-text">简单编解码器示例</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#复杂解码器"><span class="nav-number">6.2.</span> <span class="nav-text">复杂解码器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#多路分离的解码器"><span class="nav-number">6.3.</span> <span class="nav-text">多路分离的解码器</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#线程模型配置"><span class="nav-number">7.</span> <span class="nav-text">线程模型配置</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2024</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">MolZhao</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">ZYJ</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Gemini</a> v5.1.4</div>






        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

  
    <span class="site-uv">
      <i class="fa fa-user"></i> 访客数
      <span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
      人
    </span>
  

  
    <span class="site-pv">
      <i class="fa fa-eye"></i> 总访问量
      <span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
      次
    </span>
  
</div>








        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    
      <div id="needsharebutton-float">
        <span class="btn">
          <i class="fa fa-share-alt" aria-hidden="true"></i>
        </span>
      </div>
    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  










  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  <script type="text/javascript" src="/js/src/Valine.min.js"></script>
  
  <script type="text/javascript">
    var GUEST = ['nick','mail','link'];
    var guest = 'nick,mail,link';
    guest = guest.split(',').filter(item=>{
      return GUEST.indexOf(item)>-1;
    });
    new Valine({
        el: '#comments' ,
        verify: false,
        notify: false,
        appId: '18BuKzLQMbUp8ECwDBiHu6Df-MdYXbMMI',
        appKey: 'xVTsu5pYLayqSvClr36TzcAH',
        placeholder: '(•̀へ•╮)',
        avatar:'identicon',
        guest_info:guest,
        pageSize:'10' || 10,
    });

    $('.vpower').html('');
  </script>



  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "./public/search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  
  
  <link rel="stylesheet" href="/lib/needsharebutton/needsharebutton.css">

  
  
  <script src="/lib/needsharebutton/needsharebutton.js"></script>

  <script>
    
      pbOptions = {};
      
          pbOptions.iconStyle = "default";
      
          pbOptions.boxForm = "horizontal";
      
          pbOptions.position = "bottomCenter";
      
          pbOptions.networks = "Weibo,Wechat,Douban,QQZone,Twitter,Facebook";
      
      new needShareButton('#needsharebutton-postbottom', pbOptions);
    
    
      flOptions = {};
      
          flOptions.iconStyle = "default";
      
          flOptions.boxForm = "horizontal";
      
          flOptions.position = "middleRight";
      
          flOptions.networks = "Weibo,Wechat,Douban,QQZone,Twitter,Facebook";
      
      new needShareButton('#needsharebutton-float', flOptions);
    
  </script>

  

  

  

  

<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"scale":1,"hHeadPos":0.5,"vHeadPos":0.5,"jsonPath":"/live2dw/assets/assets/wanko.model.json"},"display":{"superSample":2,"width":150,"height":150,"position":"right","hOffset":30,"vOffset":20},"mobile":{"show":false,"scale":0.05},"react":{"opacityDefault":0.6,"opacityOnHover":0.2},"log":false});</script></body>
</html>
